Commit 9ca5b8c2 authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz
Browse files

scc_info: add ways to speedup scc_info

* spot/twaalgos/sccinfo.hh, spot/twaalgos/sccinfo.cc: Add an optional
argument to abort on accepting SCC, to not keep track of SCC states,
and some one_accepting_scc() method.
* NEWS: Mention it.
* bin/ltlcross.cc, spot/twaalgos/alternation.cc,
spot/twaalgos/cobuchi.cc, spot/twaalgos/degen.cc,
spot/twaalgos/determinize.cc, spot/twaalgos/dtbasat.cc,
spot/twaalgos/dtwasat.cc, spot/twaalgos/isunamb.cc,
spot/twaalgos/powerset.cc, spot/twaalgos/remfin.cc,
spot/twaalgos/sbacc.cc, spot/twaalgos/sccfilter.cc,
spot/twaalgos/totgba.cc: Adjust arguments passed to scc_info.
parent 11704d31
......@@ -41,6 +41,13 @@ New in spot 2.4.1.dev (not yet released)
- Rename three methods of spot::scc_info. New names are clearer. The
old names have been deprecated.
- scc_info now takes an optional argument to disable some feature
that are expansive and not always necessary. By default scc_info
tracks the list of all states that belong to an SCC (you may now
ask it not to), tracks the successor SCCs of each SCC (that can
but turned off), and explores all SCCs of the automaton (you may
request to stop on the first SCC that is found accepting).
- The new function scc_info::states_on_acc_cycle_of() is able to
return all states visited by any accepting cycles of the
specified SCC. It must only be called on automata with a
......
......@@ -619,7 +619,10 @@ namespace
st->edges = s.edges;
st->transitions = s.transitions;
st->acc = res->acc().num_sets();
spot::scc_info m(res);
spot::scc_info
m(res, (opt_strength
? spot::scc_info_options::TRACK_STATES
: spot::scc_info_options::NONE));
unsigned c = m.scc_count();
st->scc = c;
st->nondetstates = spot::count_nondet_states(res);
......@@ -750,16 +753,9 @@ namespace
for (size_t i = 0; i < m; ++i)
if (spot::scc_info* m = maps[i])
{
// r == true iff the automaton i is accepting.
bool r = false;
for (auto& scc: *m)
if (scc.is_accepting())
{
r = true;
break;
}
res[i] = r;
if (r)
bool i_is_accepting = m->one_accepting_scc() >= 0;
res[i] = i_is_accepting;
if (i_is_accepting)
++verified;
else
++violated;
......@@ -1301,7 +1297,8 @@ namespace
std::cerr << "info: product has " << p->num_states()
<< " st., " << p->num_edges()
<< " ed.\n";
sm = new spot::scc_info(p);
sm = new
spot::scc_info(p, spot::scc_info_options::TRACK_STATES);
}
catch (std::bad_alloc&)
{
......@@ -1334,7 +1331,8 @@ namespace
std::cerr << "info: product has " << p->num_states()
<< " st., " << p->num_edges()
<< " ed.\n";
sm = new spot::scc_info(p);
sm = new
spot::scc_info(p, spot::scc_info_options::TRACK_STATES);
}
catch (std::bad_alloc&)
{
......
......@@ -333,7 +333,8 @@ namespace spot
public:
alternation_remover(const const_twa_graph_ptr& aut)
: aut_(aut), si_(aut), class_of_(si_.scc_count(), scc_class::accept)
: aut_(aut), si_(aut, scc_info_options::TRACK_STATES),
class_of_(si_.scc_count(), scc_class::accept)
{
}
......
......@@ -202,7 +202,7 @@ namespace spot
named_states_(named_states),
res_(aug_subset_cons(ref_prod, ref_power, named_states_, pmap_)),
res_map_(res_->get_named_prop<product_states>("product-states")),
si_(scc_info(res_)),
si_(scc_info(res_, scc_info_options::TRACK_STATES)),
nb_states_(res_->num_states()),
was_rabin_(was_rabin),
orig_num_st_(orig_num_st)
......
......@@ -310,8 +310,9 @@ namespace spot
std::vector<std::pair<unsigned, bool>> lvl_cache(a->num_states());
// Compute SCCs in order to use any optimization.
std::unique_ptr<scc_info> m = use_scc ?
std::make_unique<scc_info>(a) : nullptr;
std::unique_ptr<scc_info> m = use_scc
? std::make_unique<scc_info>(a, scc_info_options::NONE)
: nullptr;
// Cache for common outgoing/incoming acceptances.
inout_acc inout(a, m.get());
......@@ -621,7 +622,7 @@ namespace spot
if (!remove_extra_scc || res_ns <= a->num_states())
return res;
scc_info si_res(res);
scc_info si_res(res, scc_info_options::TRACK_STATES);
unsigned res_scc_count = si_res.scc_count();
if (res_scc_count <= m->scc_count())
return res;
......
......@@ -589,7 +589,7 @@ namespace spot
aut2->copy_state_names_from(aut);
aut = aut2;
}
scc_info scc = scc_info(aut);
scc_info scc = scc_info(aut, scc_info_options::TRACK_SUCCS);
std::vector<bool> is_connected = find_scc_paths(scc);
bdd allap = bddtrue;
......
......@@ -316,7 +316,7 @@ namespace spot
nap = 1 << nap;
}
scc_info sm(ref);
scc_info sm(ref, scc_info_options::NONE);
// Number all the SAT variables we may need.
declare_vars(ref, d, ap, state_based, sm);
......
......@@ -557,7 +557,7 @@ namespace spot
nap = 1 << nap;
}
scc_info sm(ref);
scc_info sm(ref, scc_info_options::TRACK_STATES_IF_FIN_USED);
sm.determine_unknown_acceptance();
// Number all the SAT variables we may need.
......
......@@ -47,7 +47,8 @@ namespace spot
if (aut->num_edges() == 0)
return true;
scc_info sccmap(aut);
scc_info sccmap(aut, scc_info_options::TRACK_SUCCS |
scc_info_options::TRACK_STATES_IF_FIN_USED);
sccmap.determine_unknown_acceptance();
unsigned autsz = aut->num_states();
std::vector<bool> v;
......
......@@ -348,7 +348,7 @@ namespace spot
{
det->copy_acceptance_of(ref);
scc_info sm(det);
scc_info sm(det, scc_info_options::NONE);
unsigned scc_count = sm.scc_count();
......
......@@ -271,7 +271,7 @@ namespace spot
return nullptr;
// if is TBA type
scc_info si{aut};
scc_info si(aut, scc_info_options::TRACK_STATES);
std::vector<bool> scc_is_tba_type(si.scc_count(), false);
std::vector<bool> final(aut->edge_vector().size(), false);
std::vector<bool> keep(aut->edge_vector().size(), true);
......@@ -443,7 +443,7 @@ namespace spot
true, // complete
true, // stutter inv.
});
scc_info si(res);
scc_info si(res, scc_info_options::NONE);
// We will modify res in place, and the resulting
// automaton will only have one acceptance set.
......@@ -671,7 +671,7 @@ namespace spot
res->prop_state_acc(true);
bool sbacc = res->prop_state_acc().is_true();
scc_info si(aut);
scc_info si(aut, scc_info_options::TRACK_STATES);
unsigned nscc = si.scc_count();
std::vector<unsigned> state_map(nst);
for (unsigned n = 0; n < nscc; ++n)
......
......@@ -43,7 +43,7 @@ namespace spot
return res;
}
scc_info si(old);
scc_info si(old, scc_info_options::NONE);
unsigned ns = old->num_states();
acc_cond::mark_t all = old->acc().all_sets();
......
......@@ -288,7 +288,8 @@ namespace spot
// Compute scc_info if not supplied.
scc_info* si = given_si;
if (!si)
si = new scc_info(aut);
si = new scc_info(aut, scc_info_options::TRACK_SUCCS
| scc_info_options::TRACK_STATES_IF_FIN_USED);
si->determine_unknown_acceptance();
F filter(si, std::forward<Args>(args)...);
......
......@@ -28,6 +28,23 @@
namespace spot
{
void scc_info::report_need_track_states()
{
throw std::runtime_error
("scc_info was not run with option TRACK_STATES");
}
void scc_info::report_need_track_succs()
{
throw std::runtime_error
("scc_info was not run with option TRACK_SUCCS");
}
void scc_info::report_incompatible_stop_on_acc()
{
throw std::runtime_error
("scc_info was run with option STOP_ON_ACC");
}
namespace
{
......@@ -39,11 +56,11 @@ namespace spot
{
}
acc_cond::mark_t in_acc; // Acceptance sets on the incoming transition
acc_cond::mark_t in_acc; // Acceptance sets on the incoming transition
acc_cond::mark_t acc = 0U; // union of all acceptance marks in the SCC
acc_cond::mark_t common = -1U; // intersection of all marks in the SCC
int index; // Index of the SCC
bool trivial = true; // Whether the SCC has no cycle
int index; // Index of the SCC
bool trivial = true; // Whether the SCC has no cycle
bool accepting = false; // Necessarily accepting
};
}
......@@ -51,14 +68,21 @@ namespace spot
scc_info::scc_info(const_twa_graph_ptr aut,
unsigned initial_state,
edge_filter filter,
void* filter_data)
void* filter_data,
scc_info_options options)
: aut_(aut), initial_state_(initial_state),
filter_(filter), filter_data_(filter_data)
filter_(filter), filter_data_(filter_data),
options_(options)
{
unsigned n = aut->num_states();
sccof_.resize(n, -1U);
std::deque<unsigned> live;
if (!!(options & scc_info_options::TRACK_STATES_IF_FIN_USED)
&& aut->acc().uses_fin_acceptance())
options_ = options = options | scc_info_options::TRACK_STATES;
std::vector<unsigned> live;
live.reserve(n);
std::deque<scc> root_; // Stack of SCC roots.
std::vector<int> h_(n, 0);
// Map of visited states. Values > 0 designate maximal SCC.
......@@ -79,7 +103,6 @@ namespace spot
std::stack<stack_item> todo_;
auto& gr = aut->get_graph();
std::deque<unsigned> init_states;
std::vector<bool> init_seen(n, false);
auto push_init = [&](unsigned s)
......@@ -90,6 +113,76 @@ namespace spot
init_states.push_back(s);
};
bool track_states = !!(options & scc_info_options::TRACK_STATES);
bool track_succs = !!(options & scc_info_options::TRACK_SUCCS);
auto backtrack = [&](unsigned curr)
{
if (root_.back().index == h_[curr])
{
unsigned num = node_.size();
acc_cond::mark_t acc = root_.back().acc;
acc_cond::mark_t common = root_.back().common;
bool triv = root_.back().trivial;
node_.emplace_back(acc, common, triv);
auto& succ = node_.back().succ_;
unsigned np1 = num + 1;
auto s = live.rbegin();
do
{
sccof_[*s] = num;
h_[*s] = np1;
// Gather all successor SCCs
if (track_succs)
for (auto& t: aut->out(*s))
for (unsigned d: aut->univ_dests(t))
{
unsigned n = sccof_[d];
if (n == num || n == -1U)
continue;
// If edges are cut, we are not able to
// maintain proper successor information.
if (filter_)
switch (filter_(t, d, filter_data_))
{
case edge_filter_choice::keep:
break;
case edge_filter_choice::ignore:
case edge_filter_choice::cut:
continue;
}
succ.emplace_back(n);
}
}
while (*s++ != curr);
if (track_states)
{
auto& nbs = node_.back().states_;
nbs.insert(nbs.end(), s.base(), live.end());
}
node_.back().one_state_ = curr;
live.erase(s.base(), live.end());
if (track_succs)
{
std::sort(succ.begin(), succ.end());
succ.erase(std::unique(succ.begin(), succ.end()), succ.end());
}
bool accept = !triv && root_.back().accepting;
node_.back().accepting_ = accept;
if (accept)
one_acc_scc_ = num;
bool reject = triv ||
aut->acc().maybe_accepting(acc, common).is_false();
node_.back().rejecting_ = reject;
root_.pop_back();
}
};
// Setup depth-first search from the initial state. But we may
// have a conjunction of initial state in alternating automata.
if (initial_state_ == -1U)
......@@ -128,61 +221,7 @@ namespace spot
// remove that SCC from the ARC/ROOT stacks. We must
// discard from H all reachable states from this SCC.
assert(!root_.empty());
if (root_.back().index == h_[curr])
{
unsigned num = node_.size();
acc_cond::mark_t acc = root_.back().acc;
acc_cond::mark_t common = root_.back().common;
bool triv = root_.back().trivial;
node_.emplace_back(acc, common, triv);
// Move all elements of this SCC from the live stack
// to the the node.
auto i = std::find(live.rbegin(), live.rend(), curr);
assert(i != live.rend());
++i; // Because base() does -1
auto& nbs = node_.back().states_;
nbs.insert(nbs.end(), i.base(), live.end());
live.erase(i.base(), live.end());
std::set<unsigned> dests;
unsigned np1 = num + 1;
for (unsigned s: nbs)
{
sccof_[s] = num;
h_[s] = np1;
}
// Gather all successor SCCs
for (unsigned s: nbs)
for (auto& t: aut->out(s))
for (unsigned d: aut->univ_dests(t))
{
// If edges are cut, we are not able to
// maintain proper successor information.
if (filter_)
switch (filter_(t, d, filter_data_))
{
case edge_filter_choice::keep:
break;
case edge_filter_choice::ignore:
case edge_filter_choice::cut:
continue;
}
unsigned n = sccof_[d];
assert(n != -1U);
if (n == num)
continue;
dests.insert(n);
}
auto& succ = node_.back().succ_;
succ.insert(succ.end(), dests.begin(), dests.end());
bool accept = !triv && root_.back().accepting;
node_.back().accepting_ = accept;
bool reject = triv ||
aut->acc().maybe_accepting(acc, common).is_false();
node_.back().rejecting_ = reject;
root_.pop_back();
}
backtrack(curr);
continue;
}
......@@ -290,9 +329,22 @@ namespace spot
|| aut->acc().accepting(root_.back().acc);
// This SCC is no longer trivial.
root_.back().trivial = false;
if (root_.back().accepting
&& !!(options & scc_info_options::STOP_ON_ACC))
{
while (!todo_.empty())
{
unsigned curr = todo_.top().src;
todo_.pop();
backtrack(curr);
}
return;
}
}
}
determine_usefulness();
if (track_succs && !(options & scc_info_options::STOP_ON_ACC))
determine_usefulness();
}
void scc_info::determine_usefulness()
......@@ -317,7 +369,6 @@ namespace spot
}
}
std::set<acc_cond::mark_t> scc_info::marks_of(unsigned scc) const
{
std::set<acc_cond::mark_t> res;
......@@ -376,16 +427,18 @@ namespace spot
--s;
if (!is_rejecting_scc(s) && !is_accepting_scc(s))
{
if (!aut_->is_existential())
if (SPOT_UNLIKELY(!aut_->is_existential()))
throw std::runtime_error(
"scc_info::determine_unknown_acceptance() "
"does not support alternating automata");
if (SPOT_UNLIKELY(!(options_ & scc_info_options::TRACK_STATES)))
report_need_track_states();
auto& node = node_[s];
if (k.empty())
k.resize(aut_->num_states());
for (auto i: node.states_)
k[i] = true;
if (mask_keep_accessible_states(aut_, k, node.states_.front())
if (mask_keep_accessible_states(aut_, k, node.one_state_)
->is_empty())
node.rejecting_ = true;
else
......@@ -394,7 +447,7 @@ namespace spot
}
}
while (s);
if (changed)
if (changed && !!(options_ & scc_info_options::TRACK_SUCCS))
determine_usefulness();
}
......@@ -451,6 +504,8 @@ namespace spot
scc_info::split_on_sets(unsigned scc, acc_cond::mark_t sets,
bool preserve_names) const
{
if (SPOT_UNLIKELY(!(options_ & scc_info_options::TRACK_STATES)))
report_need_track_states();
std::vector<twa_graph_ptr> res;
std::vector<bool> seen(aut_->num_states(), false);
......@@ -527,12 +582,8 @@ namespace spot
scc_info si_tmp(aut);
unsigned scccount_tmp = si_tmp.scc_count();
for (unsigned scc_tmp = 0; scc_tmp < scccount_tmp; ++scc_tmp)
si_tmp.states_on_acc_cycle_of_rec(scc_tmp,
all_fin,
all_inf,
nb_pairs,
pairs,
res,
si_tmp.states_on_acc_cycle_of_rec(scc_tmp, all_fin, all_inf,
nb_pairs, pairs, res,
*orig_sts);
}
......@@ -559,8 +610,8 @@ namespace spot
for (unsigned i = 0; i < nb_states; ++i)
old.push_back(i);
acc_cond::mark_t all_fin = 0u;
acc_cond::mark_t all_inf = 0u;
acc_cond::mark_t all_fin = 0U;
acc_cond::mark_t all_inf = 0U;
std::tie(all_inf, all_fin) = aut_->get_acceptance().used_inf_fin_sets();
states_on_acc_cycle_of_rec(scc, all_fin, all_inf, nb_pairs, pairs, res,
......
......@@ -219,9 +219,10 @@ namespace spot
friend class scc_info;
protected:
scc_succs succ_;
std::vector<unsigned> states_; // States of the component
unsigned one_state_;
acc_cond::mark_t acc_;
acc_cond::mark_t common_;
std::vector<unsigned> states_; // States of the component
bool trivial_:1;
bool accepting_:1; // Necessarily accepting
bool rejecting_:1; // Necessarily rejecting
......@@ -286,12 +287,65 @@ namespace spot
return states_;
}
unsigned one_state() const
{
return one_state_;
}
const scc_succs& succ() const
{
return succ_;
}
};
/// Options to alter the behavior of scc_info
enum class scc_info_options
{
/// Stop exploring when an accepting SCC is found, and do not track
/// the states of each SCC.
NONE = 0,
/// Stop exploring after an accepting SCC has been found.
/// Using this option forbids future uses of is_useful_scc() and
/// is_useful_state(). Using it will also cause the output of
/// succ() to be incomplete.
STOP_ON_ACC = 1,
/// Keep a vector of all states belonging to each SCC. Using this
/// option is a precondition for using states_of(), edges_of(),
/// inner_edges_of(), states_on_acc_cycle_of(), and
/// determine_unknown_acceptance().
TRACK_STATES = 2,
/// Keep a list of successors of each SCCs.
/// Using this option is a precondition for using succ(),
/// is_useful_scc(), and is_useful_state().
TRACK_SUCCS = 4,
/// Conditionally track states if the acceptance conditions uses Fin.
/// This is sufficiant for determine_unknown_acceptance().
TRACK_STATES_IF_FIN_USED = 8,
/// Default behavior: explore everything and track states and succs.
ALL = TRACK_STATES | TRACK_SUCCS,
};
inline
bool operator!(scc_info_options me)
{
return me == scc_info_options::NONE;
}
inline
scc_info_options operator&(scc_info_options left, scc_info_options right)
{
typedef std::underlying_type_t<scc_info_options> ut;
return static_cast<scc_info_options>(static_cast<ut>(left)
& static_cast<ut>(right));
}
inline
scc_info_options operator|(scc_info_options left, scc_info_options right)
{
typedef std::underlying_type_t<scc_info_options> ut;
return static_cast<scc_info_options>(static_cast<ut>(left)
| static_cast<ut>(right));
}
/// \brief Compute an SCC map and gather assorted information.
///
......@@ -356,6 +410,8 @@ namespace spot
unsigned initial_state_;
edge_filter filter_;
void* filter_data_;
int one_acc_scc_ = -1;
scc_info_options options_;
// Update the useful_ bits. Called automatically.
void determine_usefulness();
......@@ -365,14 +421,29 @@ namespace spot
return node_[scc];
}
public:
#ifndef SWIG
private:
[[noreturn]] static void report_need_track_states();
[[noreturn]] static void report_need_track_succs();
[[noreturn]] static void report_incompatible_stop_on_acc();
#endif
// Use ~0U instead of -1U to work around a bug in Swig.
// See https://github.com/swig/swig/issues/993
public:
/// @{
/// \brief Create the scc_info map for \a aut
scc_info(const_twa_graph_ptr aut,
// Use ~0U instead of -1U to work around a bug in Swig.
// See https://github.com/swig/swig/issues/993