stength: fix detection of terminal automata

Fixes issue #553.

* spot/twaalgos/strength.cc (is_type_automaton): Make sure an
accepting SCC is not followed by a rejecting one.
(is_terminal_automaton): Mark the third-argument version deprecated.
* spot/twaalgos/strength.hh: Adjust.
* spot/twaalgos/couvreurnew.cc: Remove the inappropriate terminal
optimization.
* bin/ltlfilt.cc, spot/tl/hierarchy.cc, spot/twaalgos/gfguarantee.cc,
tests/core/ikwiad.cc: Remove usage of the third argument of
is_terminal_automaton.
* tests/core/readsave.test, tests/core/strength.test: Adjust test
cases.
* NEWS: Mention the bug.
This commit is contained in:
Alexandre Duret-Lutz 2023-11-21 16:40:33 +01:00
parent 63362d535f
commit 62fb0c354e
10 changed files with 75 additions and 82 deletions

View file

@ -549,10 +549,7 @@ namespace spot
return run_;
}
// A simple enum for the different automata strengths.
enum twa_strength { STRONG, WEAK, TERMINAL };
template<bool is_explicit, twa_strength strength>
template<bool is_explicit, bool is_strong>
class couvreur99_new : public emptiness_check, public ec_statistics
{
using T = twa_iteration<is_explicit>;
@ -696,7 +693,7 @@ namespace spot
state_t init = T::initial_state(ecs_->aut);
ecs_->h[init] = 1;
ecs_->root.push(1);
if (strength == STRONG)
if (is_strong)
arc.push({});
auto iter = T::succ(ecs_->aut, init);
todo.emplace(init, iter);
@ -706,7 +703,7 @@ namespace spot
while (!todo.empty())
{
if (strength == STRONG)
if (is_strong)
assert(ecs_->root.size() == arc.size());
// We are looking at the next successor in SUCC.
@ -727,7 +724,7 @@ namespace spot
assert(!ecs_->root.empty());
if (ecs_->root.top().index == ecs_->h[curr])
{
if (strength == STRONG)
if (is_strong)
{
assert(!arc.empty());
arc.pop();
@ -759,21 +756,7 @@ namespace spot
}
// Fetch the values we are interested in...
auto acc = succ->acc();
if (!need_accepting_run)
if (strength == TERMINAL && ecs_->aut->acc().accepting(acc))
{
// We have found an accepting SCC.
// Release all iterators in todo.
while (!todo.empty())
{
T::it_destroy(ecs_->aut, todo.top().second);
todo.pop();
dec_depth();
}
// We do not need an accepting run.
return true;
}
acc_cond::mark_t acc = succ->acc();
state_t dest = succ->dst();
// ... and point the iterator to the next successor, for
// the next iteration.
@ -788,7 +771,7 @@ namespace spot
// Yes. Bump number, stack the stack, and register its
// successors for later processing.
ecs_->root.push(++num);
if (strength == STRONG)
if (is_strong)
arc.push(acc);
iterator_t iter = T::succ(ecs_->aut, dest);
todo.emplace(dest, iter);
@ -818,7 +801,7 @@ namespace spot
while (threshold < ecs_->root.top().index)
{
assert(!ecs_->root.empty());
if (strength == STRONG)
if (is_strong)
{
assert(!arc.empty());
acc |= ecs_->root.top().condition;
@ -864,20 +847,18 @@ namespace spot
} // anonymous namespace
template<twa_strength strength>
using cna = couvreur99_new<false, strength>;
template<twa_strength strength>
using cne = couvreur99_new<true, strength>;
template<bool is_strong>
using cna = couvreur99_new<false, is_strong>;
template<bool is_strong>
using cne = couvreur99_new<true, is_strong>;
emptiness_check_ptr
get_couvreur99_new_abstract(const const_twa_ptr& a, option_map o)
{
// NB: The order of the if's matter.
if (a->prop_terminal())
return SPOT_make_shared_enabled__(cna<TERMINAL>, a, o);
if (a->prop_weak())
return SPOT_make_shared_enabled__(cna<WEAK>, a, o);
return SPOT_make_shared_enabled__(cna<STRONG>, a, o);
return SPOT_make_shared_enabled__(cna<false>, a, o);
else
return SPOT_make_shared_enabled__(cna<true>, a, o);
}
emptiness_check_ptr
@ -886,12 +867,10 @@ namespace spot
const_twa_graph_ptr ag = std::dynamic_pointer_cast<const twa_graph>(a);
if (ag) // the automaton is explicit
{
// NB: The order of the if's matter.
if (a->prop_terminal())
return SPOT_make_shared_enabled__(cne<TERMINAL>, ag, o);
if (a->prop_weak())
return SPOT_make_shared_enabled__(cne<WEAK>, ag, o);
return SPOT_make_shared_enabled__(cne<STRONG>, ag, o);
return SPOT_make_shared_enabled__(cne<false>, ag, o);
else
return SPOT_make_shared_enabled__(cne<true>, ag, o);
}
else // the automaton is abstract
{

View file

@ -57,7 +57,7 @@ namespace spot
bool want_merge_edges = false;
twa_graph_ptr aut = std::const_pointer_cast<twa_graph>(si.get_aut());
if (!is_terminal_automaton(aut, &si, true))
if (!is_terminal_automaton(aut, &si))
throw std::runtime_error("g_f_terminal() expects a terminal automaton");
// If a terminal automaton has only one SCC, it is either
@ -490,13 +490,13 @@ namespace spot
return nullptr;
scc_info si(reduced);
if (!is_terminal_automaton(reduced, &si, true))
if (!is_terminal_automaton(reduced, &si))
return nullptr;
do_g_f_terminal_inplace(si, state_based);
if (!deterministic)
{
scc_info si2(aut);
if (!is_terminal_automaton(aut, &si2, true))
if (!is_terminal_automaton(aut, &si2))
return reduced;
do_g_f_terminal_inplace(si2, state_based);
if (aut->num_states() < reduced->num_states())

View file

@ -32,8 +32,7 @@ namespace spot
namespace
{
template <bool terminal, bool inweak = false, bool set = false>
bool is_type_automaton(const twa_graph_ptr& aut, scc_info* si,
bool ignore_trivial_term = false)
bool is_type_automaton(const twa_graph_ptr& aut, scc_info* si)
{
// Create an scc_info if the user did not give one to us.
bool need_si = !si;
@ -85,36 +84,29 @@ namespace spot
break;
}
}
if (terminal && si->is_accepting_scc(i) && !is_complete_scc(*si, i))
if (terminal && is_term && si->is_accepting_scc(i))
{
is_term = false;
if (!set)
is_term = is_complete_scc(*si, i);
if (is_term)
{
for (unsigned j: si->succ(i))
if (si->is_rejecting_scc(j))
{
is_term = false;
break;
}
}
if (!is_term && !set)
break;
}
}
// A terminal automaton should accept any word that has a prefix
// leading to an accepting edge. In other words, we cannot have
// an accepting edge that goes into a rejecting SCC.
if (terminal && is_term && !ignore_trivial_term)
for (auto& e: aut->edges())
if (si->is_rejecting_scc(si->scc_of(e.dst))
&& aut->acc().accepting(e.acc))
{
is_term = false;
break;
}
exit:
if (need_si)
delete si;
if (set)
{
if (terminal)
{
if (!ignore_trivial_term)
aut->prop_terminal(is_term && is_weak);
else if (is_term && is_weak)
aut->prop_terminal(true);
}
aut->prop_terminal(is_term && is_weak);
aut->prop_weak(is_weak);
aut->prop_very_weak(is_single_state_scc && is_weak);
if (inweak)
@ -127,19 +119,23 @@ namespace spot
}
bool
is_terminal_automaton(const const_twa_graph_ptr& aut, scc_info* si,
bool ignore_trivial_term)
is_terminal_automaton(const const_twa_graph_ptr& aut, scc_info* si)
{
trival v = aut->prop_terminal();
if (v.is_known())
return v.is_true();
bool res =
is_type_automaton<true>(std::const_pointer_cast<twa_graph>(aut), si,
ignore_trivial_term);
is_type_automaton<true>(std::const_pointer_cast<twa_graph>(aut), si);
std::const_pointer_cast<twa_graph>(aut)->prop_terminal(res);
return res;
}
bool
is_terminal_automaton(const const_twa_graph_ptr& aut, scc_info* si, bool)
{
return is_terminal_automaton(aut, si);
}
bool
is_weak_automaton(const const_twa_graph_ptr& aut, scc_info* si)
{

View file

@ -25,11 +25,8 @@ namespace spot
/// \brief Check whether an automaton is terminal.
///
/// An automaton is terminal if it is weak, all its accepting SCCs
/// are complete, and no accepting transitions lead to a
/// non-accepting SCC.
///
/// If ignore_trivial_scc is set, accepting transitions from trivial
/// SCCs are ignored.
/// are complete, and no accepting SCC may lead to a non-accepting
/// SCC.
///
/// This property guarantees that a word is accepted if it has some
/// prefix that reaches an accepting transition.
@ -43,8 +40,16 @@ namespace spot
/// the prop_terminal() property of the automaton as a side-effect,
/// so further calls will return in constant-time.
SPOT_API bool
is_terminal_automaton(const const_twa_graph_ptr& aut, scc_info* sm = nullptr,
bool ignore_trivial_scc = false);
is_terminal_automaton(const const_twa_graph_ptr& aut,
scc_info* sm = nullptr);
// 3-arg form was deprecated in Spot 2.12
SPOT_DEPRECATED("is third argument of is_terminal_automaton()"
" is now ignored")
SPOT_API bool
is_terminal_automaton(const const_twa_graph_ptr& aut,
scc_info* sm, bool);
/// \brief Check whether an automaton is weak.
///