twaalgos: implement restrict_dead_end_edges_here()
Discussed in issue #587. * spot/twaalgos/deadends.cc, spot/twaalgos/deadends.hh: New files. * spot/twaalgos/Makefile.am, python/spot/impl.i: Add them. * tests/core/deadends.test, tests/python/deadends.py: New files. * tests/Makefile.am: Add them. * spot/twa/acc.cc, spot/twa/acc.hh (keep_one_inf_per_branch): New method. * bin/autfilt.cc: Learn option --restrict-dead-end-edges. * NEWS: Mention it.
This commit is contained in:
parent
f03e32619a
commit
31511e042a
11 changed files with 588 additions and 1 deletions
14
NEWS
14
NEWS
|
|
@ -1,6 +1,18 @@
|
||||||
New in spot 2.12.0.dev (not yet released)
|
New in spot 2.12.0.dev (not yet released)
|
||||||
|
|
||||||
Nothing yet.
|
Command-line tools:
|
||||||
|
|
||||||
|
- autfilt learned --restrict-dead-end-edges, to restricts labels of
|
||||||
|
edges leading to dead-ends. See the description of
|
||||||
|
restrict_dead_end_edges_here() below.
|
||||||
|
|
||||||
|
Library:
|
||||||
|
|
||||||
|
- restrict_dead_end_edges_here() can reduce non-determinism (but
|
||||||
|
not remove it) by restricting the label L of some edge (S)-L->(D)
|
||||||
|
going to a state D that does not have other successor than
|
||||||
|
itself. The conditions are detailled in the documentation of
|
||||||
|
this function.
|
||||||
|
|
||||||
New in spot 2.12 (2024-05-16)
|
New in spot 2.12 (2024-05-16)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
#include <spot/twaalgos/cleanacc.hh>
|
#include <spot/twaalgos/cleanacc.hh>
|
||||||
#include <spot/twaalgos/complement.hh>
|
#include <spot/twaalgos/complement.hh>
|
||||||
#include <spot/twaalgos/contains.hh>
|
#include <spot/twaalgos/contains.hh>
|
||||||
|
#include <spot/twaalgos/deadends.hh>
|
||||||
#include <spot/twaalgos/degen.hh>
|
#include <spot/twaalgos/degen.hh>
|
||||||
#include <spot/twaalgos/dtwasat.hh>
|
#include <spot/twaalgos/dtwasat.hh>
|
||||||
#include <spot/twaalgos/dualize.hh>
|
#include <spot/twaalgos/dualize.hh>
|
||||||
|
|
@ -148,6 +149,7 @@ enum {
|
||||||
OPT_REM_UNREACH,
|
OPT_REM_UNREACH,
|
||||||
OPT_REM_UNUSED_AP,
|
OPT_REM_UNUSED_AP,
|
||||||
OPT_REM_FIN,
|
OPT_REM_FIN,
|
||||||
|
OPT_RESTRICT_DEAD_ENDS,
|
||||||
OPT_SAT_MINIMIZE,
|
OPT_SAT_MINIMIZE,
|
||||||
OPT_SCCS,
|
OPT_SCCS,
|
||||||
OPT_SEED,
|
OPT_SEED,
|
||||||
|
|
@ -373,6 +375,9 @@ static const argp_option options[] =
|
||||||
{ "remove-dead-states", OPT_REM_DEAD, nullptr, 0,
|
{ "remove-dead-states", OPT_REM_DEAD, nullptr, 0,
|
||||||
"remove states that are unreachable, or that cannot belong to an "
|
"remove states that are unreachable, or that cannot belong to an "
|
||||||
"infinite path", 0 },
|
"infinite path", 0 },
|
||||||
|
{ "restrict-dead-end-edges", OPT_RESTRICT_DEAD_ENDS, nullptr, 0,
|
||||||
|
"restrict labels of dead-end edges, based on useful transitions of the "
|
||||||
|
"state they reach", 0 },
|
||||||
{ "simplify-acceptance", OPT_SIMPL_ACC, nullptr, 0,
|
{ "simplify-acceptance", OPT_SIMPL_ACC, nullptr, 0,
|
||||||
"simplify the acceptance condition by merging identical acceptance sets "
|
"simplify the acceptance condition by merging identical acceptance sets "
|
||||||
"and by simplifying some terms containing complementary sets", 0 },
|
"and by simplifying some terms containing complementary sets", 0 },
|
||||||
|
|
@ -715,6 +720,7 @@ static bool opt_dca = false;
|
||||||
static bool opt_streett_like = false;
|
static bool opt_streett_like = false;
|
||||||
static bool opt_enlarge_acceptance_set = false;
|
static bool opt_enlarge_acceptance_set = false;
|
||||||
static bool opt_reduce_acceptance_set = false;
|
static bool opt_reduce_acceptance_set = false;
|
||||||
|
static bool opt_restrict_dead_ends = false;
|
||||||
|
|
||||||
static spot::twa_graph_ptr
|
static spot::twa_graph_ptr
|
||||||
ensure_deterministic(const spot::twa_graph_ptr& aut, bool nonalt = false)
|
ensure_deterministic(const spot::twa_graph_ptr& aut, bool nonalt = false)
|
||||||
|
|
@ -1199,6 +1205,9 @@ parse_opt(int key, char* arg, struct argp_state*)
|
||||||
case OPT_REM_UNUSED_AP:
|
case OPT_REM_UNUSED_AP:
|
||||||
opt_rem_unused_ap = true;
|
opt_rem_unused_ap = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_RESTRICT_DEAD_ENDS:
|
||||||
|
opt_restrict_dead_ends = true;
|
||||||
|
break;
|
||||||
case OPT_SAT_MINIMIZE:
|
case OPT_SAT_MINIMIZE:
|
||||||
opt_sat_minimize = arg ? arg : "";
|
opt_sat_minimize = arg ? arg : "";
|
||||||
break;
|
break;
|
||||||
|
|
@ -1442,6 +1451,9 @@ namespace
|
||||||
else if (opt_clean_acc)
|
else if (opt_clean_acc)
|
||||||
cleanup_acceptance_here(aut);
|
cleanup_acceptance_here(aut);
|
||||||
|
|
||||||
|
if (opt_restrict_dead_ends)
|
||||||
|
restrict_dead_end_edges_here(aut);
|
||||||
|
|
||||||
if (opt_sep_sets)
|
if (opt_sep_sets)
|
||||||
separate_sets_here(aut);
|
separate_sets_here(aut);
|
||||||
if (opt_complement_acc)
|
if (opt_complement_acc)
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@
|
||||||
#include <spot/twaalgos/complete.hh>
|
#include <spot/twaalgos/complete.hh>
|
||||||
#include <spot/twaalgos/complement.hh>
|
#include <spot/twaalgos/complement.hh>
|
||||||
#include <spot/twaalgos/dbranch.hh>
|
#include <spot/twaalgos/dbranch.hh>
|
||||||
|
#include <spot/twaalgos/deadends.hh>
|
||||||
#include <spot/twaalgos/degen.hh>
|
#include <spot/twaalgos/degen.hh>
|
||||||
#include <spot/twaalgos/dot.hh>
|
#include <spot/twaalgos/dot.hh>
|
||||||
#include <spot/twaalgos/dualize.hh>
|
#include <spot/twaalgos/dualize.hh>
|
||||||
|
|
@ -717,6 +718,7 @@ def state_is_accepting(self, src) -> "bool":
|
||||||
%include <spot/twaalgos/copy.hh>
|
%include <spot/twaalgos/copy.hh>
|
||||||
%include <spot/twaalgos/complete.hh>
|
%include <spot/twaalgos/complete.hh>
|
||||||
%include <spot/twaalgos/dbranch.hh>
|
%include <spot/twaalgos/dbranch.hh>
|
||||||
|
%include <spot/twaalgos/deadends.hh>
|
||||||
%include <spot/twaalgos/degen.hh>
|
%include <spot/twaalgos/degen.hh>
|
||||||
%include <spot/twaalgos/determinize.hh>
|
%include <spot/twaalgos/determinize.hh>
|
||||||
%include <spot/twaalgos/dot.hh>
|
%include <spot/twaalgos/dot.hh>
|
||||||
|
|
|
||||||
|
|
@ -1845,6 +1845,86 @@ namespace spot
|
||||||
return force_inf_rec(&back(), rem);
|
return force_inf_rec(&back(), rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
static acc_cond::acc_code keep_one_inf_per_branch_rec
|
||||||
|
(const acc_cond::acc_word* pos, bool inf_seen)
|
||||||
|
{
|
||||||
|
auto start = pos - pos->sub.size;
|
||||||
|
switch (pos->sub.op)
|
||||||
|
{
|
||||||
|
case acc_cond::acc_op::And:
|
||||||
|
{
|
||||||
|
auto cur = --pos;
|
||||||
|
auto res = acc_cond::acc_code::t();
|
||||||
|
// Make a first pass to find Inf(...)
|
||||||
|
if (!inf_seen)
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (cur->sub.op == acc_cond::acc_op::Inf)
|
||||||
|
{
|
||||||
|
res = acc_cond::acc_code::inf(cur[-1].mark.lowest());
|
||||||
|
inf_seen = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cur -= cur->sub.size + 1;
|
||||||
|
}
|
||||||
|
while (cur > start);
|
||||||
|
// Now process the rest.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (pos->sub.op != acc_cond::acc_op::Inf)
|
||||||
|
{
|
||||||
|
auto tmp =
|
||||||
|
keep_one_inf_per_branch_rec(pos, inf_seen) &
|
||||||
|
std::move(res);
|
||||||
|
std::swap(tmp, res);
|
||||||
|
}
|
||||||
|
pos -= pos->sub.size + 1;
|
||||||
|
}
|
||||||
|
while (pos > start);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
case acc_cond::acc_op::Or:
|
||||||
|
{
|
||||||
|
--pos;
|
||||||
|
auto res = acc_cond::acc_code::f();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto tmp =
|
||||||
|
keep_one_inf_per_branch_rec(pos, inf_seen) | std::move(res);
|
||||||
|
std::swap(tmp, res);
|
||||||
|
pos -= pos->sub.size + 1;
|
||||||
|
}
|
||||||
|
while (pos > start);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
case acc_cond::acc_op::Fin:
|
||||||
|
return acc_cond::acc_code::fin(pos[-1].mark);
|
||||||
|
case acc_cond::acc_op::Inf:
|
||||||
|
if (inf_seen)
|
||||||
|
return acc_cond::acc_code::t();
|
||||||
|
else
|
||||||
|
return acc_cond::acc_code::inf(pos[-1].mark.lowest());
|
||||||
|
case acc_cond::acc_op::FinNeg:
|
||||||
|
case acc_cond::acc_op::InfNeg:
|
||||||
|
SPOT_UNREACHABLE();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
SPOT_UNREACHABLE();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
acc_cond::acc_code
|
||||||
|
acc_cond::acc_code::keep_one_inf_per_branch() const
|
||||||
|
{
|
||||||
|
if (is_t() || is_f())
|
||||||
|
return *this;
|
||||||
|
return keep_one_inf_per_branch_rec(&back(), false);
|
||||||
|
}
|
||||||
|
|
||||||
acc_cond::mark_t
|
acc_cond::mark_t
|
||||||
acc_cond::acc_code::used_sets() const
|
acc_cond::acc_code::used_sets() const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1391,6 +1391,13 @@ namespace spot
|
||||||
/// \brief For all `x` in \a m, replaces `Fin(x)` by `false`.
|
/// \brief For all `x` in \a m, replaces `Fin(x)` by `false`.
|
||||||
acc_code force_inf(mark_t m) const;
|
acc_code force_inf(mark_t m) const;
|
||||||
|
|
||||||
|
/// \brief Rewrite an acceptance condition by keeping at most
|
||||||
|
/// one Inf(x) on each dijunctive branch.
|
||||||
|
///
|
||||||
|
/// For instance `(Fin(0)&Inf(1)&(Inf(2)|Fin(3))) | Inf(4)&Inf(5)`
|
||||||
|
/// will become `(Fin(0)&Inf(1) | Inf(4)`
|
||||||
|
acc_code keep_one_inf_per_branch() const;
|
||||||
|
|
||||||
/// \brief Return the set of sets appearing in the condition.
|
/// \brief Return the set of sets appearing in the condition.
|
||||||
acc_cond::mark_t used_sets() const;
|
acc_cond::mark_t used_sets() const;
|
||||||
|
|
||||||
|
|
@ -1992,6 +1999,16 @@ namespace spot
|
||||||
return code_.inf_satisfiable(inf);
|
return code_.inf_satisfiable(inf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Rewrite an acceptance condition by keeping at most
|
||||||
|
/// one Inf(x) on each disjunctive branch.
|
||||||
|
///
|
||||||
|
/// For instance `(Fin(0)&Inf(1)&(Inf(2)|Fin(3))) | Inf(4)&Inf(5)`
|
||||||
|
/// will become `(Fin(0)&Inf(1) | Inf(4)`
|
||||||
|
acc_cond keep_one_inf_per_branch() const
|
||||||
|
{
|
||||||
|
return {num_sets(), code_.keep_one_inf_per_branch()};
|
||||||
|
}
|
||||||
|
|
||||||
/// \brief Check potential acceptance of an SCC.
|
/// \brief Check potential acceptance of an SCC.
|
||||||
///
|
///
|
||||||
/// Assuming that an SCC intersects all sets in \a
|
/// Assuming that an SCC intersects all sets in \a
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ twaalgos_HEADERS = \
|
||||||
copy.hh \
|
copy.hh \
|
||||||
cycles.hh \
|
cycles.hh \
|
||||||
dbranch.hh \
|
dbranch.hh \
|
||||||
|
deadends.hh \
|
||||||
degen.hh \
|
degen.hh \
|
||||||
determinize.hh \
|
determinize.hh \
|
||||||
dot.hh \
|
dot.hh \
|
||||||
|
|
@ -114,6 +115,7 @@ libtwaalgos_la_SOURCES = \
|
||||||
contains.cc \
|
contains.cc \
|
||||||
cycles.cc \
|
cycles.cc \
|
||||||
dbranch.cc \
|
dbranch.cc \
|
||||||
|
deadends.cc \
|
||||||
degen.cc \
|
degen.cc \
|
||||||
determinize.cc \
|
determinize.cc \
|
||||||
dot.cc \
|
dot.cc \
|
||||||
|
|
|
||||||
181
spot/twaalgos/deadends.cc
Normal file
181
spot/twaalgos/deadends.cc
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
// -*- coding: utf-8 -*-
|
||||||
|
// Copyright (C) by the Spot authors, see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This file is part of Spot, a model checking library.
|
||||||
|
//
|
||||||
|
// Spot is free software; you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Spot is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <spot/twaalgos/deadends.hh>
|
||||||
|
|
||||||
|
namespace spot
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Gather a disjunction of labels that appears on the edges of a
|
||||||
|
// dead-end state that have to be seen in order to make an
|
||||||
|
// accepting cycle.
|
||||||
|
static bdd
|
||||||
|
gather_useful_labels(const const_twa_graph_ptr& aut,
|
||||||
|
acc_cond::mark_t used_in_cond,
|
||||||
|
unsigned state)
|
||||||
|
{
|
||||||
|
// First, simplify the acceptance condition c based on the set
|
||||||
|
// of colors occurring around the state.
|
||||||
|
auto c = aut->get_acceptance();
|
||||||
|
acc_cond::mark_t used_on_no_edge = used_in_cond;
|
||||||
|
acc_cond::mark_t used_on_all_edges = used_in_cond;
|
||||||
|
for (auto& e: aut->edges())
|
||||||
|
{
|
||||||
|
used_on_no_edge -= e.acc;
|
||||||
|
used_on_all_edges &= e.acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if x appears on all edges, then
|
||||||
|
// Fin(x) = false and Inf(x) = true
|
||||||
|
if (used_on_all_edges)
|
||||||
|
c = c.remove(used_on_all_edges, false);
|
||||||
|
// if x appears on no edge at all, then
|
||||||
|
// Fin(x) = true and Inf(x) = false
|
||||||
|
if (used_on_no_edge)
|
||||||
|
c = c.remove(used_on_no_edge, true);
|
||||||
|
|
||||||
|
if (c.is_f())
|
||||||
|
return bddfalse;
|
||||||
|
if (c.is_t())
|
||||||
|
return bddtrue;
|
||||||
|
|
||||||
|
auto d = c.keep_one_inf_per_branch();
|
||||||
|
|
||||||
|
// Now look for edges that are useful to the simplified
|
||||||
|
// acceptance condition.
|
||||||
|
// We consider an edge as useful if its colors satisfy at
|
||||||
|
// least one Fin(x) or Inf(x) in the acceptance.
|
||||||
|
bdd useful = bddfalse;
|
||||||
|
for (auto& e: aut->out(state))
|
||||||
|
if (d.accepting(e.acc))
|
||||||
|
useful |= e.cond;
|
||||||
|
return useful;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
twa_graph_ptr
|
||||||
|
restrict_dead_end_edges_here(twa_graph_ptr& aut)
|
||||||
|
{
|
||||||
|
// We don't have anything to do if the automaton is deterministic.
|
||||||
|
if (aut->prop_universal())
|
||||||
|
return aut;
|
||||||
|
if (!aut->is_existential())
|
||||||
|
throw std::runtime_error
|
||||||
|
("restrict_dead_end_edges_here() does not support alternation");
|
||||||
|
unsigned ns = aut->num_states();
|
||||||
|
// Find the states that are dead ends, i.e., that
|
||||||
|
// have only themselves as successors.
|
||||||
|
std::vector<bool> dead_end_states(ns, true);
|
||||||
|
// Also record the disjunction of all self-loops around each
|
||||||
|
// state.
|
||||||
|
std::vector<bdd> self_loops(ns, bddfalse);
|
||||||
|
for (auto& e: aut->edges())
|
||||||
|
if (e.src == e.dst)
|
||||||
|
self_loops[e.src] |= e.cond;
|
||||||
|
else
|
||||||
|
dead_end_states[e.src] = false;
|
||||||
|
|
||||||
|
// If the automaton is weak, we can consider every label of
|
||||||
|
// the dead-end state as useful.
|
||||||
|
bool is_weak = aut->prop_weak().is_true();
|
||||||
|
|
||||||
|
// This will hold the labels of the useful self-loops of the the
|
||||||
|
// dead-end states. But we don't want to initialize it until we
|
||||||
|
// need it.
|
||||||
|
std::vector<bdd> dead_end_useful(is_weak ? 0U : ns, bddfalse);
|
||||||
|
std::vector<bool> dead_end_useful_computed(is_weak ? 0U : ns, false);
|
||||||
|
acc_cond::mark_t used_in_cond = aut->get_acceptance().used_sets();
|
||||||
|
|
||||||
|
std::vector<bdd> label_unions(ns, bddfalse);
|
||||||
|
bool created_false_labels = false;
|
||||||
|
bool nondeterministic_for_sure = false;
|
||||||
|
for (unsigned s = 0; s < ns; ++s)
|
||||||
|
{
|
||||||
|
// compute a union of labels per dead-end destination
|
||||||
|
for (auto& e: aut->out(s))
|
||||||
|
if (e.src != e.dst && dead_end_states[e.dst])
|
||||||
|
label_unions[e.dst] |= e.cond;
|
||||||
|
|
||||||
|
// Iterate over all edges (SRC,COND,DST), find those such that
|
||||||
|
// (1) DST is a dead-end,
|
||||||
|
// (2) Lab(DST,DST))⇒Lab(SRC,SRC)
|
||||||
|
// (3) UsefulLab(DST)⇒Lab(SRC,DST)⇒Lab(SRC,SRC)
|
||||||
|
//
|
||||||
|
// where Lab(X,Y) is the union of all labels between X and Y
|
||||||
|
// And UsefulLab(DST) are the labeled of the "useful" self
|
||||||
|
// loops of DST (see gather_useful_labels).
|
||||||
|
for (auto& e: aut->out(s))
|
||||||
|
if (e.src != e.dst && dead_end_states[e.dst])
|
||||||
|
{
|
||||||
|
if (bdd u = label_unions[e.dst], sl = self_loops[e.src];
|
||||||
|
bdd_implies(u, sl) && bdd_implies(self_loops[e.dst], sl))
|
||||||
|
{
|
||||||
|
// Find the edges of DST that are necessary to an
|
||||||
|
// accepting loop, and gather their labels.
|
||||||
|
bdd d;
|
||||||
|
if (is_weak)
|
||||||
|
{
|
||||||
|
d = self_loops[e.dst];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!dead_end_useful_computed[e.dst])
|
||||||
|
{
|
||||||
|
dead_end_useful[e.dst] =
|
||||||
|
gather_useful_labels(aut, used_in_cond, e.dst);
|
||||||
|
dead_end_useful_computed[e.dst] = true;
|
||||||
|
}
|
||||||
|
d = dead_end_useful[e.dst];
|
||||||
|
}
|
||||||
|
if (bdd_implies(d, u))
|
||||||
|
{
|
||||||
|
// Restrict the dead-end transition's label.
|
||||||
|
bdd cond = e.cond;
|
||||||
|
cond &= d;
|
||||||
|
if (cond != e.cond)
|
||||||
|
{
|
||||||
|
e.cond = cond;
|
||||||
|
if (cond == bddfalse)
|
||||||
|
created_false_labels = true;
|
||||||
|
else
|
||||||
|
nondeterministic_for_sure = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// reset unions before next iteration
|
||||||
|
for (auto& e: aut->out(s))
|
||||||
|
label_unions[e.dst] = bddfalse;
|
||||||
|
}
|
||||||
|
// Note that restricting those label will never make the automaton
|
||||||
|
// deterministic. In fact, it could make the automaton
|
||||||
|
// non-deterministic. Additionally, completeness will not be
|
||||||
|
// changed. This is because the restricted Lab(SRC,DST) still
|
||||||
|
// implies Lab(SRC,SRC).
|
||||||
|
if (nondeterministic_for_sure)
|
||||||
|
aut->prop_universal(false);
|
||||||
|
if (created_false_labels)
|
||||||
|
aut->merge_edges();
|
||||||
|
return aut;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
spot/twaalgos/deadends.hh
Normal file
51
spot/twaalgos/deadends.hh
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// -*- coding: utf-8 -*-
|
||||||
|
// Copyright (C) by the Spot authors, see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This file is part of Spot, a model checking library.
|
||||||
|
//
|
||||||
|
// Spot is free software; you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Spot is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <spot/twa/twagraph.hh>
|
||||||
|
|
||||||
|
namespace spot
|
||||||
|
{
|
||||||
|
/// \brief restrict labels from "dead-end edges"
|
||||||
|
///
|
||||||
|
/// A dead-end edge is an edge between two states S₁ and S₂ such
|
||||||
|
/// that S₂ has only itself as successor. I.e., once a run goes
|
||||||
|
/// through this "dead-end" edge, it gets stuck in S₂.
|
||||||
|
///
|
||||||
|
/// Let Lab(S,D) denote the disjunction of all labels between S and
|
||||||
|
/// D. Let UsefulLab(D,D) be the disjunction of labels of any
|
||||||
|
/// subset of self-loops of D that will intersect all accepting
|
||||||
|
/// cycles around D (one way to compute this subset is to simplify
|
||||||
|
/// the acceptance condition with keep_one_inf_per_branch(), and
|
||||||
|
/// then keep each edge that satisfy it).
|
||||||
|
///
|
||||||
|
/// Now, if the following implications are satisfied
|
||||||
|
///
|
||||||
|
/// ⎧ UsefulLab(D,D) ⇒ Lab(S,D) ⇒ Lab(S,S),<br/>
|
||||||
|
/// ⎨ <br/>
|
||||||
|
/// ⎩ Lab(D,D) ⇒ Lab(S,S).<br/>
|
||||||
|
///
|
||||||
|
/// then any edge between S and D, labeled by ℓ⊆Lab(S,D)
|
||||||
|
/// can be replaced by ℓ∩UsefulLab(D,D).
|
||||||
|
///
|
||||||
|
/// This algorithm has no effect on deterministic automata (where
|
||||||
|
/// it is not possible that Lab(S,D) ⇒ Lab(S,S)).
|
||||||
|
SPOT_API twa_graph_ptr
|
||||||
|
restrict_dead_end_edges_here(twa_graph_ptr& aut);
|
||||||
|
}
|
||||||
|
|
@ -270,6 +270,7 @@ TESTS_twa = \
|
||||||
core/dupexp.test \
|
core/dupexp.test \
|
||||||
core/exclusive-tgba.test \
|
core/exclusive-tgba.test \
|
||||||
core/remprop.test \
|
core/remprop.test \
|
||||||
|
core/deadends.test \
|
||||||
core/degendet.test \
|
core/degendet.test \
|
||||||
core/degenid.test \
|
core/degenid.test \
|
||||||
core/degenlskip.test \
|
core/degenlskip.test \
|
||||||
|
|
@ -413,6 +414,7 @@ TESTS_python = \
|
||||||
python/dbranch.py \
|
python/dbranch.py \
|
||||||
python/declenv.py \
|
python/declenv.py \
|
||||||
python/decompose_scc.py \
|
python/decompose_scc.py \
|
||||||
|
python/deadends.py \
|
||||||
python/det.py \
|
python/det.py \
|
||||||
python/dualize.py \
|
python/dualize.py \
|
||||||
python/ecfalse.py \
|
python/ecfalse.py \
|
||||||
|
|
|
||||||
70
tests/core/deadends.test
Executable file
70
tests/core/deadends.test
Executable file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (C) by the Spot authors, see the AUTHORS file for details.
|
||||||
|
#
|
||||||
|
# This file is part of Spot, a model checking library.
|
||||||
|
#
|
||||||
|
# Spot is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Spot is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
. ./defs
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# The following formula was incorrectly reduced during
|
||||||
|
# the development of --restrict-dead-edges.
|
||||||
|
out=`ltl2tgba 'X((GF!p2 R p3) & G(p3 U p2))' |
|
||||||
|
autfilt --restrict-dead --stats='%T %t'`
|
||||||
|
test "$out" = '11 11' # should be twice the same value
|
||||||
|
|
||||||
|
# The following formulas are all reduced
|
||||||
|
cat >input.ltl <<EOF
|
||||||
|
F((Gp2 & XGp3) | (F!p2 & XF!p3))
|
||||||
|
F((XGp2 & G!p1) | G(Fp1 & F!p2))
|
||||||
|
F((p3 W Gp1) W XG!p0)
|
||||||
|
X(p1 & ((p2 & Gp1) | (!p2 & F!p1))) M Gp2
|
||||||
|
FG(p0 M Xp1)
|
||||||
|
G!p2 U G(p0 & Xp1)
|
||||||
|
FG(p1 M (p0 R (((p0 & p3) | (!p0 & !p3)) M p3)))
|
||||||
|
F(G(!p1 & ((!p1 & !p3) | (p1 & p3))) | G(Fp1 & F((!p1 & p3) | (p1 & !p3))))
|
||||||
|
G!p3 & FG((p0 | p1 | X!p1) & (!p0 | Xp1) & (!p1 | Xp1))
|
||||||
|
FG(p0 & (Xp2 | (!p2 M FG!p3)) & ((p2 W GFp3) | X!p2))
|
||||||
|
FG(((p0 M Xp2) | (p1 R Fp3)) & ((!p0 W X!p2) | (!p1 U G!p3)))
|
||||||
|
F!p2 & X((Gp1 | XGp0) U XGp2)
|
||||||
|
G(Fp0 & FGp1 & F!p2)
|
||||||
|
F(G((p0 | p1) & Xp1) & FGp0)
|
||||||
|
(p0 | X(p0 M Gp3)) U X((Fp3 & Gp0) | (F!p0 & G!p3))
|
||||||
|
XF((Fp2 & XFp3) | (G!p2 & XG!p3))
|
||||||
|
FG((Gp3 | X!p0) & (Xp0 | F!p3))
|
||||||
|
(p1 & p2) M FG((p2 | XGp3) & (!p2 | XF!p3))
|
||||||
|
Fp2 W G(XGp3 M (G!p1 M Xp0))
|
||||||
|
FG(p2 M Xp1)
|
||||||
|
F(GFp1 & (Xp3 W Gp3))
|
||||||
|
F(G(p1 & !p2) | G(Fp2 & F!p1))
|
||||||
|
F(G((!p3 M p0) & Fp1) | G(F(p3 W !p0) & FG!p1))
|
||||||
|
G!p3 U XG!p0
|
||||||
|
XXF(XF!p3 | G((p1 & X(p1 | p3)) | (!p1 & X(!p1 & !p3))))
|
||||||
|
FG((Gp2 | Xp3) & (F!p2 | X!p3))
|
||||||
|
((p0 & FG!p0) | (!p0 & GFp0)) M G(!p3 & F!p2)
|
||||||
|
GFp0 & FGp1 & FGp2 & GFp3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
ltl2tgba -F input.ltl | tee output.aut |
|
||||||
|
autfilt --restrict-dead --stats="%T %t %M" |
|
||||||
|
while read in out f; do
|
||||||
|
: $in : $out : "$f"
|
||||||
|
test $in -le $out && exit 1
|
||||||
|
:
|
||||||
|
done
|
||||||
|
|
||||||
|
autcross -F output.aut --language-preserved 'autfilt --restrict-dead'
|
||||||
158
tests/python/deadends.py
Normal file
158
tests/python/deadends.py
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
|
# Copyright (C) by the Spot authors, see the AUTHORS file for details.
|
||||||
|
#
|
||||||
|
# This file is part of Spot, a model checking library.
|
||||||
|
#
|
||||||
|
# Spot is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Spot is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Test that the spot.gen package works, in particular, we want
|
||||||
|
# to make sure that the objects created from spot.gen methods
|
||||||
|
# are usable with methods from the spot package.
|
||||||
|
|
||||||
|
|
||||||
|
import spot
|
||||||
|
from unittest import TestCase
|
||||||
|
tc = TestCase()
|
||||||
|
|
||||||
|
b = spot.translate('FGb')
|
||||||
|
a = spot.translate('GFa & GFc')
|
||||||
|
p = spot.product_susp(b, a)
|
||||||
|
q = spot.scc_filter(spot.simulation(p), True)
|
||||||
|
s = p.to_str()
|
||||||
|
spot.restrict_dead_end_edges_here(p)
|
||||||
|
s += p.to_str()
|
||||||
|
# Applying it twice should not change anything
|
||||||
|
spot.restrict_dead_end_edges_here(p)
|
||||||
|
s += p.to_str()
|
||||||
|
|
||||||
|
tc.assertEqual(s, """HOA: v1
|
||||||
|
States: 2
|
||||||
|
Start: 0
|
||||||
|
AP: 3 "a" "b" "c"
|
||||||
|
acc-name: generalized-Buchi 2
|
||||||
|
Acceptance: 2 Inf(0)&Inf(1)
|
||||||
|
properties: trans-labels explicit-labels trans-acc stutter-invariant
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[t] 0
|
||||||
|
[!0&1&!2] 1
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[!0&1&2] 1 {1}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
State: 1
|
||||||
|
[!0&1&!2] 1
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[!0&1&2] 1 {1}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
--END--HOA: v1
|
||||||
|
States: 2
|
||||||
|
Start: 0
|
||||||
|
AP: 3 "a" "b" "c"
|
||||||
|
acc-name: generalized-Buchi 2
|
||||||
|
Acceptance: 2 Inf(0)&Inf(1)
|
||||||
|
properties: trans-labels explicit-labels trans-acc stutter-invariant
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[t] 0
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
State: 1
|
||||||
|
[!0&1&!2] 1
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[!0&1&2] 1 {1}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
--END--HOA: v1
|
||||||
|
States: 2
|
||||||
|
Start: 0
|
||||||
|
AP: 3 "a" "b" "c"
|
||||||
|
acc-name: generalized-Buchi 2
|
||||||
|
Acceptance: 2 Inf(0)&Inf(1)
|
||||||
|
properties: trans-labels explicit-labels trans-acc stutter-invariant
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[t] 0
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
State: 1
|
||||||
|
[!0&1&!2] 1
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[!0&1&2] 1 {1}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
--END--""")
|
||||||
|
|
||||||
|
spot.restrict_dead_end_edges_here(q)
|
||||||
|
s = q.to_str()
|
||||||
|
tc.assertEqual(s, """HOA: v1
|
||||||
|
States: 2
|
||||||
|
Start: 0
|
||||||
|
AP: 3 "a" "b" "c"
|
||||||
|
acc-name: generalized-Buchi 2
|
||||||
|
Acceptance: 2 Inf(0)&Inf(1)
|
||||||
|
properties: trans-labels explicit-labels trans-acc stutter-invariant
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[t] 0
|
||||||
|
[0&1] 1
|
||||||
|
State: 1
|
||||||
|
[!0&1&!2] 1
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[!0&1&2] 1 {1}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
--END--""")
|
||||||
|
|
||||||
|
a = spot.translate('GFa & (FGb | FGc) & GFc')
|
||||||
|
s = a.to_str()
|
||||||
|
spot.restrict_dead_end_edges_here(a)
|
||||||
|
s += a.to_str()
|
||||||
|
tc.assertEqual(s, """HOA: v1
|
||||||
|
States: 3
|
||||||
|
Start: 0
|
||||||
|
AP: 3 "a" "b" "c"
|
||||||
|
acc-name: generalized-Buchi 2
|
||||||
|
Acceptance: 2 Inf(0)&Inf(1)
|
||||||
|
properties: trans-labels explicit-labels trans-acc stutter-invariant
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[t] 0
|
||||||
|
[0&1 | 1&2] 1
|
||||||
|
[2] 2
|
||||||
|
State: 1
|
||||||
|
[!0&1&!2] 1
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[!0&1&2] 1 {1}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
State: 2
|
||||||
|
[!0&2] 2 {1}
|
||||||
|
[0&2] 2 {0 1}
|
||||||
|
--END--HOA: v1
|
||||||
|
States: 3
|
||||||
|
Start: 0
|
||||||
|
AP: 3 "a" "b" "c"
|
||||||
|
acc-name: generalized-Buchi 2
|
||||||
|
Acceptance: 2 Inf(0)&Inf(1)
|
||||||
|
properties: trans-labels explicit-labels trans-acc stutter-invariant
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[t] 0
|
||||||
|
[0&1] 1
|
||||||
|
[0&2] 2
|
||||||
|
State: 1
|
||||||
|
[!0&1&!2] 1
|
||||||
|
[0&1&!2] 1 {0}
|
||||||
|
[!0&1&2] 1 {1}
|
||||||
|
[0&1&2] 1 {0 1}
|
||||||
|
State: 2
|
||||||
|
[!0&2] 2 {1}
|
||||||
|
[0&2] 2 {0 1}
|
||||||
|
--END--""")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue