degen: detect superfluous SCCs and remove them

Suggested by Maximilien Colange.

* spot/twaalgos/degen.cc: If the output has more SCC than the input,
detect useless SCCs and remove them.
* spot/twaalgos/postproc.cc, spot/twaalgos/postproc.hh,
spot/twaalgos/degen.hh: Add support for a degen-remscc option.
* bin/spot-x.cc, NEWS: Document it.
* tests/core/degenscc.test: New file.
* tests/Makefile.am: Add it.
* tests/core/det.test: Lower some expected size (yay!).
This commit is contained in:
Alexandre Duret-Lutz 2017-09-28 21:54:49 +02:00
parent ce5e3b654f
commit 900b344c9a
9 changed files with 147 additions and 22 deletions

View file

@ -26,6 +26,7 @@
#include <vector>
#include <algorithm>
#include <iterator>
#include <memory>
#include <spot/twaalgos/sccinfo.hh>
#include <spot/twa/bddprint.hh>
@ -240,7 +241,8 @@ namespace spot
twa_graph_ptr
degeneralize_aux(const const_twa_graph_ptr& a, bool use_z_lvl,
bool use_cust_acc_orders, int use_lvl_cache,
bool skip_levels, bool ignaccsl)
bool skip_levels, bool ignaccsl,
bool remove_extra_scc)
{
if (!a->acc().is_generalized_buchi())
throw std::runtime_error
@ -249,7 +251,10 @@ namespace spot
throw std::runtime_error
("degeneralize() does not support alternation");
bool use_scc = use_lvl_cache || use_cust_acc_orders || use_z_lvl;
bool use_scc = (use_lvl_cache
|| use_cust_acc_orders
|| use_z_lvl
|| remove_extra_scc);
bdd_dict_ptr dict = a->get_dict();
@ -305,12 +310,11 @@ namespace spot
std::vector<std::pair<unsigned, bool>> lvl_cache(a->num_states());
// Compute SCCs in order to use any optimization.
scc_info* m = nullptr;
if (use_scc)
m = new scc_info(a);
std::unique_ptr<scc_info> m = use_scc ?
std::make_unique<scc_info>(a) : nullptr;
// Cache for common outgoing/incoming acceptances.
inout_acc inout(a, m);
inout_acc inout(a, m.get());
queue_t todo;
@ -337,7 +341,7 @@ namespace spot
if (!skip_levels)
break;
}
// There is not accepting level for TBA, let reuse level 0.
// There is no accepting level for TBA, let reuse level 0.
if (!want_sba && s.second == order.size())
s.second = 0;
}
@ -373,9 +377,8 @@ namespace spot
// Level cache stores one encountered level for each state
// (the value of use_lvl_cache determinates which level
// should be remembered).
// When entering an SCC first the lvl_cache is checked.
// If such state exists level from chache is used.
// should be remembered). This cache is used when
// re-entering the SCC.
if (use_lvl_cache)
{
unsigned lvl = ds.second;
@ -612,10 +615,49 @@ namespace spot
std::cout << '\n';
orders.print();
#endif
delete m;
res->merge_edges();
unsigned res_ns = res->num_states();
if (!remove_extra_scc || res_ns <= a->num_states())
return res;
scc_info si_res(res);
unsigned res_scc_count = si_res.scc_count();
if (res_scc_count <= m->scc_count())
return res;
// If we reach this place, we have more SCCs in the output than
// in the input. This means that we have created some redundant
// SCCs. Often, these are trivial SCCs created in front of
// their larger sisters, because we did not pick the correct
// level when entering the SCC for the first time, and the level
// we picked has not been seen again when exploring the SCC.
// But it could also be the case that by entering the SCC in two
// different ways, we create two clones of the SCC (I haven't
// encountered any such case, but I do not want to rule it out
// in the code below).
//
// Now we will iterate over the SCCs in topological order to
// remember the "bottomost" SCCs that contain each original
// state. If an original state is duplicated in a higher SCC,
// it can be shunted away. Amen.
std::vector<unsigned> bottomost_occurence(a->num_states());
{
unsigned n = res_scc_count;
do
for (unsigned s: si_res.states_of(--n))
bottomost_occurence[(*orig_states)[s]] = s;
while (n);
}
std::vector<unsigned> retarget(res_ns);
for (unsigned n = 0; n < res_ns; ++n)
{
unsigned other = bottomost_occurence[(*orig_states)[n]];
retarget[n] = (si_res.scc_of(n) != si_res.scc_of(other)) ? other : n;
}
for (auto& e: res->edges())
e.dst = retarget[e.dst];
res->purge_unreachable_states();
return res;
}
}
@ -623,7 +665,8 @@ namespace spot
twa_graph_ptr
degeneralize(const const_twa_graph_ptr& a,
bool use_z_lvl, bool use_cust_acc_orders,
int use_lvl_cache, bool skip_levels, bool ignaccsl)
int use_lvl_cache, bool skip_levels, bool ignaccsl,
bool remove_extra_scc)
{
// If this already a degeneralized digraph, there is nothing we
// can improve.
@ -631,13 +674,15 @@ namespace spot
return std::const_pointer_cast<twa_graph>(a);
return degeneralize_aux<true>(a, use_z_lvl, use_cust_acc_orders,
use_lvl_cache, skip_levels, ignaccsl);
use_lvl_cache, skip_levels, ignaccsl,
remove_extra_scc);
}
twa_graph_ptr
degeneralize_tba(const const_twa_graph_ptr& a,
bool use_z_lvl, bool use_cust_acc_orders,
int use_lvl_cache, bool skip_levels, bool ignaccsl)
int use_lvl_cache, bool skip_levels, bool ignaccsl,
bool remove_extra_scc)
{
// If this already a degeneralized digraph, there is nothing we
// can improve.
@ -645,6 +690,7 @@ namespace spot
return std::const_pointer_cast<twa_graph>(a);
return degeneralize_aux<false>(a, use_z_lvl, use_cust_acc_orders,
use_lvl_cache, skip_levels, ignaccsl);
use_lvl_cache, skip_levels, ignaccsl,
remove_extra_scc);
}
}

View file

@ -41,7 +41,9 @@ namespace spot
/// smallest number, 3 to keep the largest level, and 1 to keep the
/// first level found). If \a ignaccsl is set, we do not directly
/// jump to the accepting level if the entering state has an
/// accepting self-loop.
/// accepting self-loop. If \a remove_extra_scc is set (the default)
/// we ensure that the output automaton has as many SCCs as the input
/// by removing superfluous SCCs.
///
/// Any of these three options will cause the SCCs of the automaton
/// \a a to be computed prior to its actual degeneralization.
@ -65,13 +67,15 @@ namespace spot
bool use_cust_acc_orders = false,
int use_lvl_cache = 1,
bool skip_levels = true,
bool ignaccsl = false);
bool ignaccsl = false,
bool remove_extra_scc = true);
SPOT_API twa_graph_ptr
degeneralize_tba(const const_twa_graph_ptr& a, bool use_z_lvl = true,
bool use_cust_acc_orders = false,
int use_lvl_cache = 1,
bool skip_levels = true,
bool ignaccsl = false);
bool ignaccsl = false,
bool remove_extra_scc = true);
/// \@}
}

View file

@ -62,6 +62,7 @@ namespace spot
degen_cache_ = opt->get("degen-lcache", 1);
degen_lskip_ = opt->get("degen-lskip", 1);
degen_lowinit_ = opt->get("degen-lowinit", 0);
degen_remscc_ = opt->get("degen-remscc", 1);
det_scc_ = opt->get("det-scc", 1);
det_simul_ = opt->get("det-simul", 1);
det_stutter_ = opt->get("det-stutter", 1);
@ -142,7 +143,7 @@ namespace spot
auto d = degeneralize(a,
degen_reset_, degen_order_,
degen_cache_, degen_lskip_,
degen_lowinit_);
degen_lowinit_, degen_remscc_);
return do_sba_simul(d, ba_simul_);
}

View file

@ -207,6 +207,7 @@ namespace spot
int degen_cache_ = 1;
bool degen_lskip_ = true;
bool degen_lowinit_ = false;
bool degen_remscc_ = true;
bool det_scc_ = true;
bool det_simul_ = true;
bool det_stutter_ = true;