is_weak_scc and friend: make them work for alternating automata

* spot/twaalgos/isweakscc.cc, spot/twaalgos/isweakscc.hh,
spot/twaalgos/mask.cc, spot/twaalgos/mask.hh: Adjust to
work with alternating automata.
* spot/twaalgos/sccinfo.cc, spot/twaalgos/sccinfo.hh
(determine_unknown_acceptance): Do not complain about
not supporting alternating automata if there is not
indeterminate acceptance.
* spot/twaalgos/stats.cc: Fix a bug were %[iw]c was
read as %[iww]c.
* tests/core/alternating.test: Test is_inherently_weak_scc() and
is_weak_scc().
* python/spot/impl.i: Add missing python bindings
for isweakscc.hh.
This commit is contained in:
Alexandre Duret-Lutz 2017-08-03 18:27:47 +02:00
parent e041db6101
commit 223b0c6a9e
9 changed files with 154 additions and 61 deletions

View file

@ -143,6 +143,7 @@
#include <spot/twaalgos/stats.hh> #include <spot/twaalgos/stats.hh>
#include <spot/twaalgos/isdet.hh> #include <spot/twaalgos/isdet.hh>
#include <spot/twaalgos/isunamb.hh> #include <spot/twaalgos/isunamb.hh>
#include <spot/twaalgos/isweakscc.hh>
#include <spot/twaalgos/langmap.hh> #include <spot/twaalgos/langmap.hh>
#include <spot/twaalgos/simulation.hh> #include <spot/twaalgos/simulation.hh>
#include <spot/twaalgos/split.hh> #include <spot/twaalgos/split.hh>
@ -574,6 +575,7 @@ def state_is_accepting(self, src) -> "bool":
%include <spot/twaalgos/stats.hh> %include <spot/twaalgos/stats.hh>
%include <spot/twaalgos/isdet.hh> %include <spot/twaalgos/isdet.hh>
%include <spot/twaalgos/isunamb.hh> %include <spot/twaalgos/isunamb.hh>
%include <spot/twaalgos/isweakscc.hh>
%include <spot/twaalgos/simulation.hh> %include <spot/twaalgos/simulation.hh>
%include <spot/twaalgos/postproc.hh> %include <spot/twaalgos/postproc.hh>
%include <spot/twaalgos/product.hh> %include <spot/twaalgos/product.hh>

View file

@ -23,20 +23,29 @@
namespace spot namespace spot
{ {
namespace
{
[[noreturn]] static void
invalid_scc_number(const char* fn)
{
throw std::invalid_argument(std::string(fn) + "(): invalid SCC number");
}
}
bool bool
scc_has_rejecting_cycle(scc_info& map, unsigned scc) scc_has_rejecting_cycle(scc_info& map, unsigned scc)
{ {
if (SPOT_UNLIKELY(scc >= map.scc_count()))
invalid_scc_number("scc_has_rejecting_cycle");
auto aut = map.get_aut(); auto aut = map.get_aut();
if (!aut->is_existential())
throw std::runtime_error
("scc_has_rejecting_cycle() does not support alternation");
// We check that by cloning the SCC and complementing its // We check that by cloning the SCC and complementing its
// acceptance condition. // acceptance condition.
std::vector<bool> keep(aut->num_states(), false); std::vector<bool> keep(aut->num_states(), false);
auto& states = map.states_of(scc); auto& states = map.states_of(scc);
for (auto s: states) for (auto s: states)
keep[s] = true; keep[s] = true;
auto sccaut = mask_keep_accessible_states(aut, keep, states.front()); auto sccaut = mask_keep_accessible_states(aut, keep, states.front(),
/* drop_univ_branch = */ true);
sccaut->set_acceptance(sccaut->acc().num_sets(), sccaut->set_acceptance(sccaut->acc().num_sets(),
sccaut->get_acceptance().complement()); sccaut->get_acceptance().complement());
return !sccaut->is_empty(); return !sccaut->is_empty();
@ -45,9 +54,8 @@ namespace spot
bool bool
is_inherently_weak_scc(scc_info& map, unsigned scc) is_inherently_weak_scc(scc_info& map, unsigned scc)
{ {
if (!map.get_aut()->is_existential()) if (SPOT_UNLIKELY(scc >= map.scc_count()))
throw std::runtime_error invalid_scc_number("is_inherently_weak_scc");
("is_inherently_weak_scc() does not support alternation");
// Weak SCCs are inherently weak. // Weak SCCs are inherently weak.
if (is_weak_scc(map, scc)) if (is_weak_scc(map, scc))
return true; return true;
@ -60,10 +68,8 @@ namespace spot
bool bool
is_weak_scc(scc_info& map, unsigned scc) is_weak_scc(scc_info& map, unsigned scc)
{ {
if (!map.get_aut()->is_existential()) if (SPOT_UNLIKELY(scc >= map.scc_count()))
throw std::runtime_error invalid_scc_number("is_weak_scc");
("is_weak_scc() does not support alternation");
// Rejecting SCCs are weak. // Rejecting SCCs are weak.
if (map.is_rejecting_scc(scc)) if (map.is_rejecting_scc(scc))
return true; return true;
@ -74,10 +80,9 @@ namespace spot
bool bool
is_complete_scc(scc_info& map, unsigned scc) is_complete_scc(scc_info& map, unsigned scc)
{ {
if (SPOT_UNLIKELY(scc >= map.scc_count()))
invalid_scc_number("is_complete_scc");
auto a = map.get_aut(); auto a = map.get_aut();
if (!a->is_existential())
throw std::runtime_error
("is_complete_scc() does not support alternation");
for (auto s: map.states_of(scc)) for (auto s: map.states_of(scc))
{ {
bool has_succ = false; bool has_succ = false;
@ -85,8 +90,16 @@ namespace spot
for (auto& t: a->out(s)) for (auto& t: a->out(s))
{ {
has_succ = true; has_succ = true;
if (map.scc_of(t.dst) == scc) bool in = true;
sumall |= t.cond; for (auto d: a->univ_dests(t.dst))
if (map.scc_of(d) != scc)
{
in = false;
break;
}
if (!in)
continue;
sumall |= t.cond;
if (sumall == bddtrue) if (sumall == bddtrue)
break; break;
} }
@ -99,9 +112,8 @@ namespace spot
bool bool
is_terminal_scc(scc_info& map, unsigned scc) is_terminal_scc(scc_info& map, unsigned scc)
{ {
if (!map.get_aut()->is_existential()) if (SPOT_UNLIKELY(scc >= map.scc_count()))
throw std::runtime_error invalid_scc_number("is_terminal_scc");
("is_terminal_scc() does not support alternation");
// If all transitions use all acceptance conditions, the SCC is weak. // If all transitions use all acceptance conditions, the SCC is weak.
return (map.is_accepting_scc(scc) return (map.is_accepting_scc(scc)

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2012, 2013, 2014 Laboratoire de Recherche et // Copyright (C) 2012, 2013, 2014, 2017 Laboratoire de Recherche et
// Développement de l'Epita (LRDE). // Développement de l'Epita (LRDE).
// //
// This file is part of Spot, a model checking library. // This file is part of Spot, a model checking library.
@ -54,7 +54,9 @@ namespace spot
/// \brief Whether the SCC number \a scc in \a map is complete. /// \brief Whether the SCC number \a scc in \a map is complete.
/// ///
/// An SCC is complete iff for all states and all label there exists /// An SCC is complete iff for all states and all label there exists
/// a transition that stays into this SCC. /// a transition that stays into this SCC. For this function,
/// universal transitions are considered in the SCC if all there
/// destination are into the SCC.
SPOT_API bool SPOT_API bool
is_complete_scc(scc_info& map, unsigned scc); is_complete_scc(scc_info& map, unsigned scc);

View file

@ -69,7 +69,8 @@ namespace spot
twa_graph_ptr mask_keep_accessible_states(const const_twa_graph_ptr& in, twa_graph_ptr mask_keep_accessible_states(const const_twa_graph_ptr& in,
std::vector<bool>& to_keep, std::vector<bool>& to_keep,
unsigned int init) unsigned int init,
bool drop_univ_branches)
{ {
if (to_keep.size() < in->num_states()) if (to_keep.size() < in->num_states())
to_keep.resize(in->num_states(), false); to_keep.resize(in->num_states(), false);
@ -83,9 +84,21 @@ namespace spot
acc_cond::mark_t&, acc_cond::mark_t&,
unsigned dst) unsigned dst)
{ {
if (!to_keep[src] || !to_keep[dst]) if (!to_keep[src])
{
cond = bddfalse;
return;
}
bool want = false;
for (auto d: in->univ_dests(dst))
if (to_keep[d])
{
want = true;
break;
}
if (!want)
cond = bddfalse; cond = bddfalse;
}, init); }, init, drop_univ_branches);
return res; return res;
} }

View file

@ -44,16 +44,16 @@ namespace spot
/// `aut->get_named_prop<std::vector<unsigned>>("original-states")` /// `aut->get_named_prop<std::vector<unsigned>>("original-states")`
/// to retrieve it. /// to retrieve it.
/// ///
/// If \a drop_univ_branches branch is set, universal branching is replaced
/// by existential branching during the copy.
///
/// \param init The optional new initial state. /// \param init The optional new initial state.
template<typename Trans> template<typename Trans>
void transform_accessible(const const_twa_graph_ptr& old, void transform_accessible(const const_twa_graph_ptr& old,
twa_graph_ptr& cpy, twa_graph_ptr& cpy,
Trans trans, unsigned int init) Trans trans, unsigned int init,
bool drop_univ_branches = false)
{ {
if (!old->is_existential())
throw std::runtime_error
("transform_accessible() does not support alternation");
std::vector<unsigned> todo; std::vector<unsigned> todo;
std::vector<unsigned> seen(old->num_states(), -1U); std::vector<unsigned> seen(old->num_states(), -1U);
@ -75,7 +75,27 @@ namespace spot
return tmp; return tmp;
}; };
cpy->set_init_state(new_state(init)); // Deal with alternating automata, possibly.
std::map<std::vector<unsigned>, unsigned> uniq;
auto new_univ_state =
[&](unsigned old_state) -> unsigned
{
if (!old->is_univ_dest(old_state))
return new_state(old_state);
std::vector<unsigned> tmp;
for (auto s: old->univ_dests(old_state))
tmp.emplace_back(new_state(s));
std::sort(tmp.begin(), tmp.end());
tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
auto p = uniq.emplace(tmp, 0);
if (p.second)
p.first->second =
cpy->get_graph().new_univ_dests(tmp.begin(), tmp.end());
return p.first->second;
};
cpy->set_init_state(new_univ_state(init));
if (seen[init] != 0) if (seen[init] != 0)
throw std::runtime_error throw std::runtime_error
("the destination automaton of transform_accessible() should be empty"); ("the destination automaton of transform_accessible() should be empty");
@ -93,13 +113,15 @@ namespace spot
bdd cond = t.cond; bdd cond = t.cond;
acc_cond::mark_t acc = t.acc; acc_cond::mark_t acc = t.acc;
trans(t.src, cond, acc, t.dst); trans(t.src, cond, acc, t.dst);
if (cond == bddfalse)
if (cond != bddfalse) continue;
cpy->new_edge(new_src, new_state(t.dst), if (drop_univ_branches)
cond, acc); for (unsigned d: old->univ_dests(t.dst))
cpy->new_edge(new_src, new_state(d), cond, acc);
else
cpy->new_edge(new_src, new_univ_state(t.dst), cond, acc);
} }
} }
orig_states->shrink_to_fit(); orig_states->shrink_to_fit();
} }
@ -186,9 +208,13 @@ namespace spot
/// will be set to \a init. Only states that are accessible from \a /// will be set to \a init. Only states that are accessible from \a
/// init via states in \a to_keep will be preserved. /// init via states in \a to_keep will be preserved.
/// ///
/// If \a drop_univ_branches branch is set, universal branching is replaced
/// by existential branching during the copy.
///
/// \see mask_keep_states /// \see mask_keep_states
SPOT_API SPOT_API
twa_graph_ptr mask_keep_accessible_states(const const_twa_graph_ptr& in, twa_graph_ptr mask_keep_accessible_states(const const_twa_graph_ptr& in,
std::vector<bool>& to_keep, std::vector<bool>& to_keep,
unsigned int init); unsigned int init,
bool drop_univ_branches = false);
} }

View file

@ -367,15 +367,15 @@ namespace spot
void scc_info::determine_unknown_acceptance() void scc_info::determine_unknown_acceptance()
{ {
if (!aut_->is_existential())
throw std::runtime_error("scc_info::determine_unknown_acceptance() "
"does not support alternating automata");
std::vector<bool> k; std::vector<bool> k;
unsigned n = scc_count(); unsigned n = scc_count();
bool changed = false; bool changed = false;
for (unsigned s = 0; s < n; ++s) for (unsigned s = 0; s < n; ++s)
if (!is_rejecting_scc(s) && !is_accepting_scc(s)) if (!is_rejecting_scc(s) && !is_accepting_scc(s))
{ {
if (!aut_->is_existential())
throw std::runtime_error("scc_info::determine_unknown_acceptance() "
"does not support alternating automata");
auto& node = node_[s]; auto& node = node_[s];
if (k.empty()) if (k.empty())
k.resize(aut_->num_states()); k.resize(aut_->num_states());

View file

@ -30,13 +30,15 @@ namespace spot
{ {
struct keep_all struct keep_all
{ {
template <typename Edge> template <typename Iterator>
bool operator()(const Edge&) const noexcept bool operator()(Iterator, Iterator) const noexcept
{ {
return true; return true;
} }
}; };
// Keep only transitions that have at least one destination in the
// current SCC.
struct keep_inner_scc struct keep_inner_scc
{ {
private: private:
@ -48,10 +50,17 @@ namespace spot
{ {
} }
template <typename Edge> template <typename Iterator>
bool operator()(const Edge& ed) const noexcept bool operator()(Iterator begin, Iterator end) const noexcept
{ {
return sccof_[ed.dst] == desired_scc_; bool want = false;
while (begin != end)
if (sccof_[*begin++] == desired_scc_)
{
want = true;
break;
}
return want;
} }
}; };
@ -79,6 +88,7 @@ namespace spot
const typename Graph::state_vector, const typename Graph::state_vector,
typename Graph::state_vector>::type typename Graph::state_vector>::type
sv_t; sv_t;
typedef const typename Graph::dests_vector_t dv_t;
protected: protected:
state_iterator pos_; state_iterator pos_;
@ -86,6 +96,8 @@ namespace spot
unsigned t_; unsigned t_;
tv_t* tv_; tv_t* tv_;
sv_t* sv_; sv_t* sv_;
dv_t* dv_;
Filter filt_; Filter filt_;
void inc_state_maybe_() void inc_state_maybe_()
@ -100,17 +112,28 @@ namespace spot
inc_state_maybe_(); inc_state_maybe_();
} }
bool ignore_current()
{
unsigned dst = (*this)->dst;
if ((int)dst >= 0)
// Non-universal branching => a single destination.
return !filt_(&(*this)->dst, 1 + &(*this)->dst);
// Universal branching => multiple destinations.
const unsigned* d = dv_->data() + ~dst;
return !filt_(d + 1, d + *d + 1);
}
public: public:
scc_edge_iterator(state_iterator begin, state_iterator end, scc_edge_iterator(state_iterator begin, state_iterator end,
tv_t* tv, sv_t* sv, Filter filt) noexcept tv_t* tv, sv_t* sv, dv_t* dv, Filter filt) noexcept
: pos_(begin), end_(end), t_(0), tv_(tv), sv_(sv), filt_(filt) : pos_(begin), end_(end), t_(0), tv_(tv), sv_(sv), dv_(dv), filt_(filt)
{ {
if (pos_ == end_) if (pos_ == end_)
return; return;
t_ = (*sv_)[*pos_].succ; t_ = (*sv_)[*pos_].succ;
inc_state_maybe_(); inc_state_maybe_();
while (pos_ != end_ && !filt_(**this)) while (pos_ != end_ && ignore_current())
inc_(); inc_();
} }
@ -118,7 +141,7 @@ namespace spot
{ {
do do
inc_(); inc_();
while (pos_ != end_ && !filt_(**this)); while (pos_ != end_ && ignore_current());
return *this; return *this;
} }
@ -158,29 +181,31 @@ namespace spot
typedef scc_edge_iterator<Graph, Filter> iter_t; typedef scc_edge_iterator<Graph, Filter> iter_t;
typedef typename iter_t::tv_t tv_t; typedef typename iter_t::tv_t tv_t;
typedef typename iter_t::sv_t sv_t; typedef typename iter_t::sv_t sv_t;
typedef typename iter_t::dv_t dv_t;
typedef typename iter_t::state_iterator state_iterator; typedef typename iter_t::state_iterator state_iterator;
private: private:
state_iterator begin_; state_iterator begin_;
state_iterator end_; state_iterator end_;
tv_t* tv_; tv_t* tv_;
sv_t* sv_; sv_t* sv_;
dv_t* dv_;
Filter filt_; Filter filt_;
public: public:
scc_edges(state_iterator begin, state_iterator end, scc_edges(state_iterator begin, state_iterator end,
tv_t* tv, sv_t* sv, Filter filt) noexcept tv_t* tv, sv_t* sv, dv_t* dv, Filter filt) noexcept
: begin_(begin), end_(end), tv_(tv), sv_(sv), filt_(filt) : begin_(begin), end_(end), tv_(tv), sv_(sv), dv_(dv), filt_(filt)
{ {
} }
iter_t begin() const iter_t begin() const
{ {
return {begin_, end_, tv_, sv_, filt_}; return {begin_, end_, tv_, sv_, dv_, filt_};
} }
iter_t end() const iter_t end() const
{ {
return {end_, end_, nullptr, nullptr, filt_}; return {end_, end_, nullptr, nullptr, nullptr, filt_};
} }
}; };
} }
@ -408,23 +433,24 @@ namespace spot
auto& states = states_of(scc); auto& states = states_of(scc);
return {states.begin(), states.end(), return {states.begin(), states.end(),
&aut_->edge_vector(), &aut_->states(), &aut_->edge_vector(), &aut_->states(),
&aut_->get_graph().dests_vector(),
internal::keep_all()}; internal::keep_all()};
} }
/// \brief A fake container to iterate over all edges between /// \brief A fake container to iterate over all edges between
/// states of an SCC. /// states of an SCC.
/// ///
/// The difference with edges_of() is that inner_edges_of() ignores /// The difference with edges_of() is that inner_edges_of()
/// edges leaving the SCC are ignored. /// ignores edges leaving the SCC are ignored. In the case of
/// alternating automaton, an edge is considered to be part of the
/// SCC of one of its destination is in the SCC.
internal::scc_edges<const twa_graph::graph_t, internal::keep_inner_scc> internal::scc_edges<const twa_graph::graph_t, internal::keep_inner_scc>
inner_edges_of(unsigned scc) const inner_edges_of(unsigned scc) const
{ {
if (!aut_->is_existential())
throw std::runtime_error
("inner_edges_of(): alternating automata are not supported");
auto& states = states_of(scc); auto& states = states_of(scc);
return {states.begin(), states.end(), return {states.begin(), states.end(),
&aut_->edge_vector(), &aut_->states(), &aut_->edge_vector(), &aut_->states(),
&aut_->get_graph().dests_vector(),
internal::keep_inner_scc(sccof_, scc)}; internal::keep_inner_scc(sccof_, scc)};
} }

View file

@ -256,15 +256,25 @@ namespace spot
break; break;
case 'i': case 'i':
if (pos[1] == 'w') if (pos[1] == 'w')
inherently_weak = true; {
inherently_weak = true;
++pos;
}
else else
error(std::string(pos, pos + 2)); {
error(std::string(pos, pos + 2));
}
break; break;
case 'I': case 'I':
if (pos[1] == 'W') if (pos[1] == 'W')
non_inherently_weak = true; {
non_inherently_weak = true;
++pos;
}
else else
error(std::string(pos, pos + 2)); {
error(std::string(pos, pos + 2));
}
break; break;
case ' ': case ' ':
case '\t': case '\t':

View file

@ -1840,6 +1840,7 @@ State: 2 "t"
EOF EOF
autfilt --tgba in 2>out && exit 1 autfilt --tgba in 2>out && exit 1
grep 'autfilt.*weak.*alternating' out grep 'autfilt.*weak.*alternating' out
test '0 2 2 1' = "`autfilt --stats='%[Wiw]c %[w]c %[iw]c %[W]c' in`"
cat >in <<EOF cat >in <<EOF
HOA: v1 HOA: v1
@ -1860,3 +1861,4 @@ State: 3 /*{0}*/
EOF EOF
autfilt --tgba in 2>out && exit 1 autfilt --tgba in 2>out && exit 1
grep 'autfilt.*weak.*alternating' out grep 'autfilt.*weak.*alternating' out
test '2 0 2 2' = "`autfilt --stats='%[Wiw]c %[w]c %[iw]c %[W]c' in`"