improve partial_degeneralize() on several cases

On these 4 cases, added to pdegen.py, and supplied by Florian Renkin,
partial_degeneralize() is now at least as good as degeneralize_tba(),
and sometimes better.  This is achieved as follows: (1) a
propagate_marks procedure is introduced to propagate marks as far as
possible on the automaton (e.g., common outgoing marks can be push
onto the incoming transitions and vice-versa), (2) the
degeneralization order is compute dynamically, and (3) whenever and
fully-accepting transition is taken in the original automaton, the
destination level is chosen to be the highest existing level.

* spot/twaalgos/degen.cc,
spot/twaalgos/degen.hh (propagate_marks_vector, propagate_marks_here):
New functions.
(partial_degeneralize): Improve, as described above.
* tests/python/pdegen.py: Add test cases.
This commit is contained in:
Alexandre Duret-Lutz 2020-02-02 23:01:33 +01:00
parent da3333477f
commit f1008c156b
4 changed files with 313 additions and 64 deletions

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2012-2019 Laboratoire de Recherche
// Copyright (C) 2012-2020 Laboratoire de Recherche
// et Développement de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
@ -122,7 +122,7 @@ namespace spot
unsigned n = a->num_states();
acc_cond::mark_t all = a_->acc().all_sets();
// slot 2 will hold acceptance mark that are common to the
// incoming transitions of each state. For know with all
// incoming transitions of each state. For now with all
// marks if there is some incoming edge. The next loop will
// constrain this value.
for (auto& e: a_->edges())
@ -176,36 +176,59 @@ namespace spot
// Order of accepting sets (for one SCC)
class acc_order final
{
std::vector<std::pair<acc_cond::mark_t, unsigned>> found_;
std::vector<unsigned> order_;
acc_cond::mark_t found_;
public:
unsigned
next_level(int slevel, acc_cond::mark_t set, bool skip_levels)
acc_order(acc_cond::mark_t all)
{
// Update the order with any new set we discover
if (auto newsets = set - found_)
{
newsets.fill(std::back_inserter(order_));
found_ |= newsets;
}
unsigned count = all.count();
found_.emplace_back(all, count);
order_.insert(order_.begin(), count, 0);
}
unsigned next = slevel;
while (next < order_.size() && set.has(order_[next]))
unsigned
next_level(unsigned slevel, acc_cond::mark_t set)
{
unsigned last = order_.size();
if (last == 0)
return slevel;
unsigned index = order_[slevel];
while (slevel < last
&& (found_[index].first & set) == found_[index].first)
slevel += found_[index++].second;
if (slevel == last)
return slevel;
if (acc_cond::mark_t match = (found_[index].first & set))
{
++next;
if (!skip_levels)
break;
unsigned matchsize = match.count();
found_[index].first -= match;
found_[index].second -= matchsize;
found_.emplace(found_.begin() + index, match, matchsize);
slevel += matchsize;
// update order_
unsigned opos = 0;
unsigned fpos = 0;
for (auto& p: found_)
{
for (unsigned n = 0; n < p.second; ++n)
order_[opos++] = fpos;
++fpos;
}
}
return next;
return slevel;
}
void
print(int scc)
{
std::cout << "Order_" << scc << ":\t";
for (auto i: found_)
std::cout << i.first << ' ';
std::cout << " // ";
for (auto i: order_)
std::cout << i << ", ";
std::cout << i << ' ';
std::cout << '\n';
}
};
@ -213,27 +236,26 @@ namespace spot
// Accepting order for each SCC
class scc_orders final
{
std::map<int, acc_order> orders_;
bool skip_levels_;
std::vector<acc_order> orders_;
public:
scc_orders(bool skip_levels):
skip_levels_(skip_levels)
scc_orders(acc_cond::mark_t all, unsigned scc_count)
: orders_(scc_count, acc_order(all))
{
}
unsigned
next_level(int scc, int slevel, acc_cond::mark_t set)
{
return orders_[scc].next_level(slevel, set, skip_levels_);
return orders_[scc].next_level(slevel, set);
}
void
print()
{
std::map<int, acc_order>::iterator i;
for (i = orders_.begin(); i != orders_.end(); i++)
i->second.print(i->first);
unsigned sz = orders_.size();
for (unsigned i = 0; i < sz; ++i)
orders_[i].print(i);
}
};
@ -348,9 +370,6 @@ namespace spot
order.emplace_back(i - 1);
}
// Initialize scc_orders
scc_orders orders(skip_levels);
// and vice-versa.
ds2num_map ds2num;
@ -370,6 +389,11 @@ namespace spot
? std::make_unique<scc_info>(a, scc_info_options::NONE)
: nullptr;
// Initialize scc_orders
std::unique_ptr<scc_orders> orders = use_cust_acc_orders
? std::make_unique<scc_orders>(a->acc().all_sets(), m->scc_count())
: nullptr;
// Cache for common outgoing/incoming acceptances.
inout_acc inout(a, m.get());
@ -390,7 +414,7 @@ namespace spot
{
auto set = inout.common_inout_acc(s.first);
if (SPOT_UNLIKELY(use_cust_acc_orders))
s.second = orders.next_level(m->initial(), s.second, set);
s.second = orders->next_level(m->initial(), s.second, set);
else
while (s.second < order.size() && set.has(order[s.second]))
{
@ -589,7 +613,7 @@ namespace spot
// for this scc
if (use_cust_acc_orders)
{
d.second = orders.next_level(scc, next, acc);
d.second = orders->next_level(scc, next, acc);
}
// Else compute level according the global acc order
else
@ -628,7 +652,7 @@ namespace spot
// In case we are building a TBA is_acc has to be
// set differently for each edge, and
// we do not need to stay use final level.
// we do not need to stay on final level.
if (!want_sba)
{
is_acc = d.second == order.size();
@ -640,7 +664,7 @@ namespace spot
{
if (use_cust_acc_orders)
{
d.second = orders.next_level(scc, d.second, acc);
d.second = orders->next_level(scc, d.second, acc);
}
else
{
@ -670,7 +694,7 @@ namespace spot
for (auto i: order)
std::cout << i << ", ";
std::cout << '\n';
orders.print();
orders->print();
#endif
res->merge_edges();
if (remove_extra_scc)
@ -808,21 +832,40 @@ namespace spot
update_acc_for_partial_degen(acc, todegen, tostrip, degenmark);
res->set_acceptance(acc);
std::vector<unsigned> order;
for (unsigned set: todegen.sets())
order.push_back(set);
//auto* names = new std::vector<std::string>;
//res->set_named_prop("state-names", names);
// auto* names = new std::vector<std::string>;
// res->set_named_prop("state-names", names);
auto orig_states = new std::vector<unsigned>();
auto levels = new std::vector<unsigned>();
orig_states->reserve(a->num_states()); // likely more are needed.
unsigned ns = a->num_states();
orig_states->reserve(ns); // likely more are needed.
levels->reserve(a->num_states());
res->set_named_prop("original-states", orig_states);
res->set_named_prop("degen-levels", levels);
scc_info si_orig(a, scc_info_options::NONE);
auto marks = propagate_marks_vector(a, &si_orig);
std::vector<unsigned> highest_level(ns, 0);
// Compute the marks that are common to all incoming or all
// outgoing transitions of each state, ignoring self-loops and
// out-of-SCC transitions. Note that because
// propagate_marks_verctor() has been used, the intersection
// of all incoming marks is equal to the intersection of all
// outgoing marks unless the state has no predecessor or no
// successor. We take the outgoing marks because states without
// successor are useless (but states without predecessors are not).
std::vector<acc_cond::mark_t> inout(ns, todegen);
for (auto& e: a->edges())
if (e.src != e.dst && si_orig.scc_of(e.src) == si_orig.scc_of(e.dst))
{
unsigned idx = a->edge_number(e);
inout[e.src] &= marks[idx];
}
scc_orders orders(todegen, si_orig.scc_count());
unsigned ordersize = todegen.count();
// degen_states -> new state numbers
ds2num_map ds2num;
@ -834,25 +877,41 @@ namespace spot
if (di != ds2num.end())
return di->second;
highest_level[ds.first] =
std::max(highest_level[ds.first], ds.second);
unsigned ns = res->new_state();
ds2num[ds] = ns;
todo.emplace_back(ds);
//std::ostringstream os;
//os << ds.first << ',' << ds.second;
//names->push_back(os.str());
// std::ostringstream os;
// os << ds.first << ',' << ds.second;
// names->push_back(os.str());
assert(ns == orig_states->size());
orig_states->emplace_back(ds.first);
levels->emplace_back(ds.second);
return ns;
};
degen_state s(a->get_init_state_number(), 0);
unsigned init = a->get_init_state_number();
degen_state s(init, 0);
// The initial level can be anything. As a heuristic, pretend we
// have just seen the acceptance condition common to incoming
// edges.
s.second = orders.next_level(si_orig.scc_of(init), 0, inout[init]);
if (s.second == ordersize)
s.second = 0;
new_state(s);
// A list of edges are that "all accepting" and whose destination
// could be redirected to a higher level.
std::vector<unsigned> allaccedges;
while (!todo.empty())
{
s = todo.front();
todo.pop_front();
s = todo.back();
todo.pop_back();
int src = ds2num[s];
unsigned slevel = s.second;
@ -860,22 +919,39 @@ namespace spot
unsigned scc_src = si_orig.scc_of(orig_src);
for (auto& e: a->out(orig_src))
{
bool saveedge = false;
unsigned nextlvl = slevel;
acc_cond::mark_t accepting = {};
if (si_orig.scc_of(e.dst) == scc_src)
{
while (nextlvl < order.size() && e.acc.has(order[nextlvl]))
++nextlvl;
if (nextlvl == order.size())
unsigned idx = a->edge_number(e);
acc_cond::mark_t acc = marks[idx] & todegen;
nextlvl = orders.next_level(scc_src, nextlvl, acc);
if (nextlvl == ordersize)
{
accepting = degenmark;
nextlvl = 0;
if ((e.acc & todegen) != todegen)
while (nextlvl < order.size()
&& e.acc.has(order[nextlvl]))
++nextlvl;
if ((acc & todegen) != todegen)
{
nextlvl = orders.next_level(scc_src, nextlvl, acc);
}
else
{
// Because we have seen all sets on this
// transition, we can jump to any level we
// like. As a heuristic, let's jump to the
// highest existing level.
nextlvl = highest_level[e.dst];
if (nextlvl == 0 // probably a new state ->
// use inout for now
&& inout[e.dst] != todegen)
nextlvl = orders.next_level(scc_src, nextlvl,
inout[e.dst]);
// It's possible that we have not yet seen the
// highest level yet, so let's save this edge
// and revisit this issue at the end.
saveedge = true;
}
}
accepting |= e.acc.strip(tostrip);
}
@ -886,13 +962,87 @@ namespace spot
degen_state ds_dst(e.dst, nextlvl);
unsigned dst = new_state(ds_dst);
res->new_edge(src, dst, e.cond, accepting);
unsigned idx = res->new_edge(src, dst, e.cond, accepting);
if (saveedge)
allaccedges.push_back(idx);
}
}
// Raise the destination of the "all-accepting" edges to the
// highest existing level.
auto& ev = res->edge_vector();
for (unsigned idx: allaccedges)
{
unsigned dst = ev[idx].dst;
unsigned orig_dst = (*orig_states)[dst];
unsigned hl = highest_level[orig_dst];
ev[idx].dst = ds2num[degen_state{orig_dst, hl}];
}
//orders.print();
res->merge_edges();
keep_bottommost_copies(res, si_orig, orig_states);
return res;
}
std::vector<acc_cond::mark_t>
propagate_marks_vector(const const_twa_graph_ptr& aut,
scc_info* si)
{
bool own_si = true;
if (si)
own_si = false;
else
si = new scc_info(aut);
unsigned ns = aut->num_states();
acc_cond::mark_t allm = aut->acc().all_sets();
unsigned es = aut->edge_vector().size();
std::vector<acc_cond::mark_t> marks(es, acc_cond::mark_t{});
const auto& edges = aut->edge_vector();
for (unsigned e = 1; e < es; ++e)
marks[e] = edges[e].acc;
std::vector<acc_cond::mark_t> common_in(ns, allm);
std::vector<acc_cond::mark_t> common_out(ns, allm);
for (;;)
{
bool changed = false;
for (auto& e: aut->edges())
if (e.src != e.dst && si->scc_of(e.src) == si->scc_of(e.dst))
{
unsigned idx = aut->edge_number(e);
common_in[e.dst] &= marks[idx];
common_out[e.src] &= marks[idx];
}
for (auto& e: aut->edges())
if (e.src != e.dst && si->scc_of(e.src) == si->scc_of(e.dst))
{
unsigned idx = aut->edge_number(e);
auto acc = marks[idx] | common_in[e.src] | common_out[e.dst];
if (acc != marks[idx])
{
marks[idx] = acc;
changed = true;
}
}
if (!changed)
break;
std::fill(common_in.begin(), common_in.end(), allm);
std::fill(common_out.begin(), common_out.end(), allm);
}
if (own_si)
delete si;
return marks;
}
void propagate_marks_here(twa_graph_ptr& aut, scc_info* si)
{
auto marks = propagate_marks_vector(aut, si);
for (auto& e: aut->edges())
{
unsigned idx = aut->edge_number(e);
e.acc = marks[idx];
}
}
}

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2012-2015, 2017-2019 Laboratoire de
// Copyright (C) 2012-2015, 2017-2020 Laboratoire de
// Recherche et Développement de l'Epita.
//
// This file is part of Spot, a model checking library.
@ -23,6 +23,8 @@
namespace spot
{
class scc_info;
/// \ingroup twa_acc_transform
/// \brief Degeneralize a spot::tgba into an equivalent sba with
/// only one acceptance condition.
@ -101,4 +103,29 @@ namespace spot
SPOT_API twa_graph_ptr
partial_degeneralize(const const_twa_graph_ptr& a,
acc_cond::mark_t todegen);
/// \ingroup twa_algorithms
/// \brief Propagate marks around the automaton
///
/// For each state of the automaton, marks that are common
/// to all input transitions will be pushed on the outgoing
/// transitions, and marks that are common to all outgoing
/// transitions will be pulled to the input transitions.
/// This considers only transitions that are not self-loops
/// and that belong to some SCC. If an scc_info has already
/// been built, pass it as \a si to avoid building it again.
///
/// Two variants of the algorithm are provided. One modifies
/// the automaton in place; the second returns a vector of marks
/// indexed by transition numbers.
///
/// @{
SPOT_API std::vector<acc_cond::mark_t>
propagate_marks_vector(const const_twa_graph_ptr& aut,
scc_info* si = nullptr);
SPOT_API void
propagate_marks_here(twa_graph_ptr& aut,
scc_info* si = nullptr);
/// @}
}