Commit 223b0c6a authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz
Browse files

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.
parent e041db61
......@@ -143,6 +143,7 @@
#include <spot/twaalgos/stats.hh>
#include <spot/twaalgos/isdet.hh>
#include <spot/twaalgos/isunamb.hh>
#include <spot/twaalgos/isweakscc.hh>
#include <spot/twaalgos/langmap.hh>
#include <spot/twaalgos/simulation.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/isdet.hh>
%include <spot/twaalgos/isunamb.hh>
%include <spot/twaalgos/isweakscc.hh>
%include <spot/twaalgos/simulation.hh>
%include <spot/twaalgos/postproc.hh>
%include <spot/twaalgos/product.hh>
......
......@@ -23,20 +23,29 @@
namespace spot
{
namespace
{
[[noreturn]] static void
invalid_scc_number(const char* fn)
{
throw std::invalid_argument(std::string(fn) + "(): invalid SCC number");
}
}
bool
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();
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
// acceptance condition.
std::vector<bool> keep(aut->num_states(), false);
auto& states = map.states_of(scc);
for (auto s: states)
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->get_acceptance().complement());
return !sccaut->is_empty();
......@@ -45,9 +54,8 @@ namespace spot
bool
is_inherently_weak_scc(scc_info& map, unsigned scc)
{
if (!map.get_aut()->is_existential())
throw std::runtime_error
("is_inherently_weak_scc() does not support alternation");
if (SPOT_UNLIKELY(scc >= map.scc_count()))
invalid_scc_number("is_inherently_weak_scc");
// Weak SCCs are inherently weak.
if (is_weak_scc(map, scc))
return true;
......@@ -60,10 +68,8 @@ namespace spot
bool
is_weak_scc(scc_info& map, unsigned scc)
{
if (!map.get_aut()->is_existential())
throw std::runtime_error
("is_weak_scc() does not support alternation");
if (SPOT_UNLIKELY(scc >= map.scc_count()))
invalid_scc_number("is_weak_scc");
// Rejecting SCCs are weak.
if (map.is_rejecting_scc(scc))
return true;
......@@ -74,10 +80,9 @@ namespace spot
bool
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();
if (!a->is_existential())
throw std::runtime_error
("is_complete_scc() does not support alternation");
for (auto s: map.states_of(scc))
{
bool has_succ = false;
......@@ -85,8 +90,16 @@ namespace spot
for (auto& t: a->out(s))
{
has_succ = true;
if (map.scc_of(t.dst) == scc)
sumall |= t.cond;
bool in = true;
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)
break;
}
......@@ -99,9 +112,8 @@ namespace spot
bool
is_terminal_scc(scc_info& map, unsigned scc)
{
if (!map.get_aut()->is_existential())
throw std::runtime_error
("is_terminal_scc() does not support alternation");
if (SPOT_UNLIKELY(scc >= map.scc_count()))
invalid_scc_number("is_terminal_scc");
// If all transitions use all acceptance conditions, the SCC is weak.
return (map.is_accepting_scc(scc)
......
// -*- 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).
//
// 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.
///
/// 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
is_complete_scc(scc_info& map, unsigned scc);
......
......@@ -69,7 +69,8 @@ namespace spot
twa_graph_ptr mask_keep_accessible_states(const const_twa_graph_ptr& in,
std::vector<bool>& to_keep,
unsigned int init)
unsigned int init,
bool drop_univ_branches)
{
if (to_keep.size() < in->num_states())
to_keep.resize(in->num_states(), false);
......@@ -83,9 +84,21 @@ namespace spot
acc_cond::mark_t&,
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;
}, init);
}, init, drop_univ_branches);
return res;
}
......
......@@ -44,16 +44,16 @@ namespace spot
/// `aut->get_named_prop<std::vector<unsigned>>("original-states")`
/// 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.
template<typename Trans>
void transform_accessible(const const_twa_graph_ptr& old,
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> seen(old->num_states(), -1U);
......@@ -75,7 +75,27 @@ namespace spot
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)
throw std::runtime_error
("the destination automaton of transform_accessible() should be empty");
......@@ -93,13 +113,15 @@ namespace spot
bdd cond = t.cond;
acc_cond::mark_t acc = t.acc;
trans(t.src, cond, acc, t.dst);
if (cond != bddfalse)
cpy->new_edge(new_src, new_state(t.dst),
cond, acc);
if (cond == bddfalse)
continue;
if (drop_univ_branches)
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();
}
......@@ -186,9 +208,13 @@ namespace spot
/// will be set to \a init. Only states that are accessible from \a
/// 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
SPOT_API
twa_graph_ptr mask_keep_accessible_states(const const_twa_graph_ptr& in,
std::vector<bool>& to_keep,
unsigned int init);
unsigned int init,
bool drop_univ_branches = false);
}
......@@ -367,15 +367,15 @@ namespace spot
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;
unsigned n = scc_count();
bool changed = false;
for (unsigned s = 0; s < n; ++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];
if (k.empty())
k.resize(aut_->num_states());
......
......@@ -30,13 +30,15 @@ namespace spot
{
struct keep_all
{
template <typename Edge>
bool operator()(const Edge&) const noexcept
template <typename Iterator>
bool operator()(Iterator, Iterator) const noexcept
{
return true;
}
};
// Keep only transitions that have at least one destination in the
// current SCC.
struct keep_inner_scc
{
private:
......@@ -48,10 +50,17 @@ namespace spot
{
}
template <typename Edge>
bool operator()(const Edge& ed) const noexcept
template <typename Iterator>
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,
typename Graph::state_vector>::type
sv_t;
typedef const typename Graph::dests_vector_t dv_t;
protected:
state_iterator pos_;
......@@ -86,6 +96,8 @@ namespace spot
unsigned t_;
tv_t* tv_;
sv_t* sv_;
dv_t* dv_;
Filter filt_;
void inc_state_maybe_()
......@@ -100,17 +112,28 @@ namespace spot
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:
scc_edge_iterator(state_iterator begin, state_iterator end,
tv_t* tv, sv_t* sv, Filter filt) noexcept
: pos_(begin), end_(end), t_(0), tv_(tv), sv_(sv), filt_(filt)
tv_t* tv, sv_t* sv, dv_t* dv, Filter filt) noexcept
: pos_(begin), end_(end), t_(0), tv_(tv), sv_(sv), dv_(dv), filt_(filt)
{
if (pos_ == end_)
return;
t_ = (*sv_)[*pos_].succ;
inc_state_maybe_();
while (pos_ != end_ && !filt_(**this))
while (pos_ != end_ && ignore_current())
inc_();
}
......@@ -118,7 +141,7 @@ namespace spot
{
do
inc_();
while (pos_ != end_ && !filt_(**this));
while (pos_ != end_ && ignore_current());
return *this;
}
......@@ -158,29 +181,31 @@ namespace spot
typedef scc_edge_iterator<Graph, Filter> iter_t;
typedef typename iter_t::tv_t tv_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;
private:
state_iterator begin_;
state_iterator end_;
tv_t* tv_;
sv_t* sv_;
dv_t* dv_;
Filter filt_;
public:
scc_edges(state_iterator begin, state_iterator end,
tv_t* tv, sv_t* sv, Filter filt) noexcept
: begin_(begin), end_(end), tv_(tv), sv_(sv), filt_(filt)
tv_t* tv, sv_t* sv, dv_t* dv, Filter filt) noexcept
: begin_(begin), end_(end), tv_(tv), sv_(sv), dv_(dv), filt_(filt)
{
}
iter_t begin() const
{
return {begin_, end_, tv_, sv_, filt_};
return {begin_, end_, tv_, sv_, dv_, filt_};
}
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);
return {states.begin(), states.end(),
&aut_->edge_vector(), &aut_->states(),
&aut_->get_graph().dests_vector(),
internal::keep_all()};
}
/// \brief A fake container to iterate over all edges between
/// states of an SCC.
///
/// The difference with edges_of() is that inner_edges_of() ignores
/// edges leaving the SCC are ignored.
/// The difference with edges_of() is that inner_edges_of()
/// 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>
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);
return {states.begin(), states.end(),
&aut_->edge_vector(), &aut_->states(),
&aut_->get_graph().dests_vector(),
internal::keep_inner_scc(sccof_, scc)};
}
......
......@@ -256,15 +256,25 @@ namespace spot
break;
case 'i':
if (pos[1] == 'w')
inherently_weak = true;
{
inherently_weak = true;
++pos;
}
else
error(std::string(pos, pos + 2));
{
error(std::string(pos, pos + 2));
}
break;
case 'I':
if (pos[1] == 'W')
non_inherently_weak = true;
{
non_inherently_weak = true;
++pos;
}
else
error(std::string(pos, pos + 2));
{
error(std::string(pos, pos + 2));
}
break;
case ' ':
case '\t':
......
......@@ -1840,6 +1840,7 @@ State: 2 "t"
EOF
autfilt --tgba in 2>out && exit 1
grep 'autfilt.*weak.*alternating' out
test '0 2 2 1' = "`autfilt --stats='%[Wiw]c %[w]c %[iw]c %[W]c' in`"
cat >in <<EOF
HOA: v1
......@@ -1860,3 +1861,4 @@ State: 3 /*{0}*/
EOF
autfilt --tgba in 2>out && exit 1
grep 'autfilt.*weak.*alternating' out
test '2 0 2 2' = "`autfilt --stats='%[Wiw]c %[w]c %[iw]c %[W]c' in`"
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment