alternation: speed up remove_alternation when few labels are used

Related to issue #566.

* spot/twaalgos/alternation.cc (alternation_remover::run): Here.
* tests/core/566.test: Augment test case.
* NEWS: Mention the change.
This commit is contained in:
Alexandre Duret-Lutz 2024-03-17 22:42:18 +01:00
parent 1e512d422b
commit f26f3243dd
3 changed files with 102 additions and 38 deletions

5
NEWS
View file

@ -150,7 +150,10 @@ New in spot 2.11.6.dev (not yet released)
- spot::dualize() learned a trick to be faster on states that have - spot::dualize() learned a trick to be faster on states that have
less outgoing edges than atomic proposition declared on the less outgoing edges than atomic proposition declared on the
automaton. (Issue #566.) automaton. spot::remove_alternation() learned a similar trick,
except it isn't applied at the state level but of the entire
alternating use few distinct labels. These two changes speed up
the complementation of very weak automata. (Issue #566.)
- [Potential backward incompatibility] spot::dualize() does not call - [Potential backward incompatibility] spot::dualize() does not call
cleanup_acceptance() anymore. This change ensures that the dual cleanup_acceptance() anymore. This change ensures that the dual

View file

@ -22,6 +22,7 @@
#include <spot/twaalgos/alternation.hh> #include <spot/twaalgos/alternation.hh>
#include <spot/twaalgos/sccinfo.hh> #include <spot/twaalgos/sccinfo.hh>
#include <spot/misc/minato.hh> #include <spot/misc/minato.hh>
#include <spot/misc/bddlt.hh>
namespace spot namespace spot
{ {
@ -361,7 +362,7 @@ namespace spot
(has_reject_more_ + reject_1_count_) > SPOT_MAX_ACCSETS) (has_reject_more_ + reject_1_count_) > SPOT_MAX_ACCSETS)
return nullptr; return nullptr;
// Rejecting SCCs of size 1 can be handled using genralized // Rejecting SCCs of size 1 can be handled using generalized
// Büchi acceptance, using one set per SCC, as in Gastin & // Büchi acceptance, using one set per SCC, as in Gastin &
// Oddoux CAV'01. See also Boker & et al. ICALP'10. Larger // Oddoux CAV'01. See also Boker & et al. ICALP'10. Larger
// rejecting SCCs require a more expensive procedure known as // rejecting SCCs require a more expensive procedure known as
@ -375,6 +376,54 @@ namespace spot
// This will raise an exception if we request too many sets. // This will raise an exception if we request too many sets.
res->set_generalized_buchi(has_reject_more_ + reject_1_count_); res->set_generalized_buchi(has_reject_more_ + reject_1_count_);
// Before we start, let's decide how we will iterate on the
// BDD that we use to encode the transition function. We have
// two way of doing so: iterating over 2^AP, or precomputing a
// set of "separated labels" that cover all labels. The
// latter is a good idea if that set is much smaller than
// 2^AP, but we cannot always know that before hand.
std::vector<bdd> separated_labels;
unsigned n_ap = aut_->ap().size();
// If |AP| is small, don't bother with the computation of
// separated labels.
bool will_use_labels = n_ap > 5;
if (will_use_labels)
{
std::set<bdd, bdd_less_than> all_labels;
// Gather all labels, but stop if we see too many.
// The threshold below is arbitrary.
unsigned max_labels = 100 * n_ap;
for (auto& e: aut_->edges())
{
if (all_labels.insert(e.cond).second)
if (all_labels.size() > max_labels)
{
will_use_labels = false;
break;
}
}
if (will_use_labels)
{
separated_labels.reserve(all_labels.size());
separated_labels.push_back(bddtrue);
for (auto& lab: all_labels)
{
// make sure don't realloc during the loop
separated_labels.reserve(separated_labels.size() * 2);
// Do not use a range-based or iterator-based for loop
// here, as push_back invalidates the end iterator.
for (unsigned cur = 0, sz = separated_labels.size();
cur < sz; ++cur)
if (bdd common = separated_labels[cur] & lab;
common != bddfalse)
{
separated_labels[cur] -= lab;
separated_labels.push_back(common);
}
}
}
}
// We for easier computation of outgoing sets, we will // We for easier computation of outgoing sets, we will
// represent states using BDD variables. // represent states using BDD variables.
allocate_state_vars(); allocate_state_vars();
@ -459,10 +508,11 @@ namespace spot
bdd ap = bdd_exist(bdd_support(bs), all_vars_); bdd ap = bdd_exist(bdd_support(bs), all_vars_);
bdd all_letters = bdd_exist(bs, all_vars_); bdd all_letters = bdd_exist(bs, all_vars_);
// First loop over all possible valuations atomic properties. // Given a label, and BDD expression representing
for (bdd oneletter: minterms_of(all_letters, ap)) // the combination of destinations, create the edges.
auto create_edges = [&](bdd label, bdd dest_formula)
{ {
minato_isop isop(bdd_restrict(bs, oneletter)); minato_isop isop(dest_formula);
bdd dest; bdd dest;
while ((dest = isop.next()) != bddfalse) while ((dest = isop.next()) != bddfalse)
{ {
@ -494,9 +544,17 @@ namespace spot
unsigned d = new_state(v, has_mark); unsigned d = new_state(v, has_mark);
if (has_mark) if (has_mark)
m.set(0); m.set(0);
res->new_edge(s, d, oneletter, all_marks - m); res->new_edge(s, d, label, all_marks - m);
}
} }
};
if (!will_use_labels)
// Loop over all possible valuations atomic properties.
for (bdd oneletter: minterms_of(all_letters, ap))
create_edges(oneletter, bdd_restrict(bs, oneletter));
else
for (bdd label: separated_labels)
create_edges(label, bdd_relprod(label, bs, res->ap_vars()));
} }
res->merge_edges(); res->merge_edges();
return res; return res;

View file

@ -135,3 +135,6 @@ EOF
# but it is difficult to test so in the test suite. # but it is difficult to test so in the test suite.
res=`autfilt --dualize 21.hoa --stats='%S %E %T %s %e %t'` res=`autfilt --dualize 21.hoa --stats='%S %E %T %s %e %t'`
test "$res" = "5 13 85 6 13 12582912" test "$res" = "5 13 85 6 13 12582912"
res=`autfilt --complement 21.hoa --stats='%S %E %T %s %e %t'`
test "$res" = "5 13 85 5 11 10485760"