Commit 6459877a authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz

overhaul the stutter-invariance checks

* spot/twaalgos/stutter.cc, spot/twaalgos/stutter.hh: Cleanup and
document the api.
* spot/twa/twa.hh, doc/mainpage.dox: Add a stutter-invariant section.
* tests/python/stutter-inv-states.ipynb: Rename as ...
* tests/python/stutter-inv.ipynb: ... this, and add more comments.
* tests/Makefile.am, doc/org/tut.org: Adjust renaming.
* bench/stutter/stutter_invariance_randomgraph.cc,
bench/stutter/stutter_invariance_formulas.cc,
bench/stutter/Makefile.am: Make it compile again.
* bin/autfilt.cc: Call inplace variants.
* NEWS: Mention the overhaul.
parent 2222661f
...@@ -126,6 +126,10 @@ New in spot 2.4.1.dev (not yet released) ...@@ -126,6 +126,10 @@ New in spot 2.4.1.dev (not yet released)
can be passed to disable this behavior (or use -x degen-remscc=0 can be passed to disable this behavior (or use -x degen-remscc=0
from the command-line). from the command-line).
- The functions for detecting stutter-invariant formulas or automata
have been overhauled. Their interface changed slightly. They are
now fully documented.
- In addition to detecting stutter-invariant formulas/automata, some - In addition to detecting stutter-invariant formulas/automata, some
can now study a stutter-sensitive automaton and detect the subset can now study a stutter-sensitive automaton and detect the subset
of states that are stutter-invariant. See of states that are stutter-invariant. See
...@@ -146,6 +150,14 @@ New in spot 2.4.1.dev (not yet released) ...@@ -146,6 +150,14 @@ New in spot 2.4.1.dev (not yet released)
spot::scc_info::marks(), spot::scc_info::marks_of() and spot::scc_info::marks(), spot::scc_info::marks_of() and
spot::scc_info::acc_sets_of() respectively. spot::scc_info::acc_sets_of() respectively.
Backward incompatible changes:
- The spot::closure(), spot::sl2(), spot::is_stutter_invariant()
functions no longuer takes && arguments. The former two have
spot::closure_inplace() and spot::sl2_inplace() variant. These
function also do not take to list of atomic propositions as an
argument anymore.
Bugs fixed: Bugs fixed:
- Automata produced by "genaut --ks-nca=N" were incorrectly marked - Automata produced by "genaut --ks-nca=N" were incorrectly marked
......
## -*- coding: utf-8 -*- ## -*- coding: utf-8 -*-
## Copyright (C) 2014, 2015 Laboratoire de Recherche et Développement ## Copyright (C) 2014, 2015, 2017 Laboratoire de Recherche et Développement
## de l'Epita (LRDE). ## de l'Epita (LRDE).
## ##
## This file is part of Spot, a model checking library. ## This file is part of Spot, a model checking library.
...@@ -24,7 +24,8 @@ AM_CXXFLAGS = $(WARNING_CXXFLAGS) ...@@ -24,7 +24,8 @@ AM_CXXFLAGS = $(WARNING_CXXFLAGS)
LDADD = \ LDADD = \
$(top_builddir)/bin/libcommon.a \ $(top_builddir)/bin/libcommon.a \
$(top_builddir)/lib/libgnu.la \ $(top_builddir)/lib/libgnu.la \
$(top_builddir)/spot/libspot.la $(top_builddir)/spot/libspot.la \
$(top_builddir)/buddy/src/libbddx.la
bin_PROGRAMS = stutter_invariance_randomgraph \ bin_PROGRAMS = stutter_invariance_randomgraph \
stutter_invariance_formulas stutter_invariance_formulas
......
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2014, 2015, 2016 Laboratoire de Recherche et // Copyright (C) 2014, 2015, 2016, 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.
...@@ -66,9 +66,8 @@ namespace ...@@ -66,9 +66,8 @@ namespace
spot::twa_graph_ptr a = trans.run(f); spot::twa_graph_ptr a = trans.run(f);
spot::twa_graph_ptr na = trans.run(spot::formula::Not(f)); spot::twa_graph_ptr na = trans.run(spot::formula::Not(f));
spot::atomic_prop_set* ap = spot::atomic_prop_collect(f); spot::atomic_prop_set* ap = spot::atomic_prop_collect(f);
bdd apdict = spot::atomic_prop_collect_as_bdd(f, a);
std::cout << formula << ',' << ap->size() << ','; std::cout << formula << ',' << ap->size() << ',';
delete ap;
stats.print(a); stats.print(a);
stats.print(na); stats.print(na);
...@@ -82,9 +81,9 @@ namespace ...@@ -82,9 +81,9 @@ namespace
sw.start(); sw.start();
bool res = spot::is_stutter_invariant(std::move(dup_a), bool res = spot::is_stutter_invariant(std::move(dup_a),
std::move(dup_na), std::move(dup_na),
apdict, algo); algo);
auto time = sw.stop(); auto time = sw.stop();
std::cout<< time << ','; std::cout << time << ',';
if (algo > 1 && prev != res) if (algo > 1 && prev != res)
{ {
...@@ -96,7 +95,6 @@ namespace ...@@ -96,7 +95,6 @@ namespace
prev = res; prev = res;
} }
std::cout << prev << '\n'; std::cout << prev << '\n';
delete ap;
return 0; return 0;
} }
}; };
......
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2014, 2015 Laboratoire de Recherche et // Copyright (C) 2014, 2015, 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.
...@@ -77,8 +77,7 @@ main(int argc, char** argv) ...@@ -77,8 +77,7 @@ main(int argc, char** argv)
do do
{ {
spot::srand(++seed); spot::srand(++seed);
a = spot::random_graph(states_n, d, &ap, dict, 2, 0.1, 0.5, a = spot::random_graph(states_n, d, &ap, dict, 2, 0.1, 0.5, true);
true);
} }
while (a->is_empty()); while (a->is_empty());
auto na = spot::remove_fin(spot::dualize(a)); auto na = spot::remove_fin(spot::dualize(a));
...@@ -102,7 +101,7 @@ main(int argc, char** argv) ...@@ -102,7 +101,7 @@ main(int argc, char** argv)
sw.start(); sw.start();
bool res = spot::is_stutter_invariant(std::move(dup_a), bool res = spot::is_stutter_invariant(std::move(dup_a),
std::move(dup_na), std::move(dup_na),
apdict, algo); algo);
auto time = sw.stop(); auto time = sw.stop();
std::cout << time; std::cout << time;
if (algo > 1 && res != prev) if (algo > 1 && res != prev)
......
...@@ -1160,11 +1160,11 @@ namespace ...@@ -1160,11 +1160,11 @@ namespace
aut = opt->excl_ap.constrain(aut, false); aut = opt->excl_ap.constrain(aut, false);
if (opt_destut) if (opt_destut)
aut = spot::closure(std::move(aut)); aut = spot::closure_inplace(std::move(aut));
if (opt_instut == 1) if (opt_instut == 1)
aut = spot::sl(std::move(aut)); aut = spot::sl(std::move(aut));
else if (opt_instut == 2) else if (opt_instut == 2)
aut = spot::sl2(std::move(aut)); aut = spot::sl2_inplace(std::move(aut));
if (!opt_keep_states.empty()) if (!opt_keep_states.empty())
aut = mask_keep_accessible_states(aut, opt_keep_states, aut = mask_keep_accessible_states(aut, opt_keep_states,
......
...@@ -19,11 +19,12 @@ ...@@ -19,11 +19,12 @@
/// ///
/// \section pointers Handy starting points /// \section pointers Handy starting points
/// ///
/// \li spot::formula Base class for an LTL or PSL formula. /// \li spot::formula Base class for an LTL or PSL formula.
/// \li spot::parse_infix_psl() Parsing a text string into a /// \li spot::parse_infix_psl() Parsing a text string into a
/// spot::formula. /// spot::formula.
/// \li spot::twa Base class for Transition-based /// \li spot::twa Base class for Transition-based
/// ω-Automata. /// ω-Automata.
/// \li spot::twa_algorithms Algorithms on ω-Automata.
/// \li spot::translator Convert a spot::formula into a /// \li spot::translator Convert a spot::formula into a
/// spot::twa. /// spot::twa.
/// \li spot::kripke Base class for Kripke structures. /// \li spot::kripke Base class for Kripke structures.
......
...@@ -83,4 +83,4 @@ real notebooks instead. ...@@ -83,4 +83,4 @@ real notebooks instead.
- [[https://spot.lrde.epita.fr/ipynb/atva16-fig2a.html][=atva16-fig2a.ipynb=]] first example from our [[https://www.lrde.epita.fr/~adl/dl/adl/duret.16.atva2.pdf][ATVA'16 tool paper]]. - [[https://spot.lrde.epita.fr/ipynb/atva16-fig2a.html][=atva16-fig2a.ipynb=]] first example from our [[https://www.lrde.epita.fr/~adl/dl/adl/duret.16.atva2.pdf][ATVA'16 tool paper]].
- [[https://spot.lrde.epita.fr/ipynb/atva16-fig2b.html][=atva16-fig2b.ipynb=]] second example from our [[https://www.lrde.epita.fr/~adl/dl/adl/duret.16.atva2.pdf][ATVA'16 tool paper]]. - [[https://spot.lrde.epita.fr/ipynb/atva16-fig2b.html][=atva16-fig2b.ipynb=]] second example from our [[https://www.lrde.epita.fr/~adl/dl/adl/duret.16.atva2.pdf][ATVA'16 tool paper]].
- [[https://spot.lrde.epita.fr/ipynb/alternation.html][=alternation.ipynb=]] examples of alternating automata. - [[https://spot.lrde.epita.fr/ipynb/alternation.html][=alternation.ipynb=]] examples of alternating automata.
- [[https://spot.lrde.epita.fr/ipynb/stutter-inv-states.html][=stutter-inv-states.ipynb=]] detecting stutter-invariant formulas and states. - [[https://spot.lrde.epita.fr/ipynb/stutter-inv.html][=stutter-inv.ipynb=]] working with stutter-invariant formulas properties.
...@@ -1687,6 +1687,9 @@ namespace spot ...@@ -1687,6 +1687,9 @@ namespace spot
/// \addtogroup twa_io Input/Output of TωA /// \addtogroup twa_io Input/Output of TωA
/// \ingroup twa_algorithms /// \ingroup twa_algorithms
/// \addtogroup stutter_inv Stutter-invariance checks
/// \ingroup twa_algorithms
/// \addtogroup twa_ltl Translating LTL formulas into TωA /// \addtogroup twa_ltl Translating LTL formulas into TωA
/// \ingroup twa_algorithms /// \ingroup twa_algorithms
......
...@@ -210,12 +210,11 @@ namespace spot ...@@ -210,12 +210,11 @@ namespace spot
class tgbasl final : public twa class tgbasl final : public twa
{ {
public: public:
tgbasl(const const_twa_ptr& a, bdd atomic_propositions) tgbasl(const_twa_ptr a)
: twa(a->get_dict()), a_(a), aps_(atomic_propositions) : twa(a->get_dict()), a_(a)
{ {
get_dict()->register_all_propositions_of(&a_, this); copy_ap_of(a);
assert(num_sets() == 0); copy_acceptance_of(a_);
set_generalized_buchi(a_->num_sets());
} }
virtual const state* get_init_state() const override virtual const state* get_init_state() const override
...@@ -227,7 +226,7 @@ namespace spot ...@@ -227,7 +226,7 @@ namespace spot
{ {
const state_tgbasl* s = down_cast<const state_tgbasl*>(state); const state_tgbasl* s = down_cast<const state_tgbasl*>(state);
return new twasl_succ_iterator(a_->succ_iter(s->real_state()), s, return new twasl_succ_iterator(a_->succ_iter(s->real_state()), s,
a_->get_dict(), aps_); a_->get_dict(), ap_vars());
} }
virtual std::string format_state(const state* state) const override virtual std::string format_state(const state* state) const override
...@@ -240,14 +239,13 @@ namespace spot ...@@ -240,14 +239,13 @@ namespace spot
private: private:
const_twa_ptr a_; const_twa_ptr a_;
bdd aps_;
}; };
typedef std::shared_ptr<tgbasl> tgbasl_ptr; typedef std::shared_ptr<tgbasl> tgbasl_ptr;
inline tgbasl_ptr make_tgbasl(const const_twa_ptr& aut, bdd ap) inline tgbasl_ptr make_tgbasl(const const_twa_ptr& aut)
{ {
return std::make_shared<tgbasl>(aut, ap); return std::make_shared<tgbasl>(aut);
} }
...@@ -272,22 +270,11 @@ namespace spot ...@@ -272,22 +270,11 @@ namespace spot
} }
twa_graph_ptr twa_graph_ptr
sl(const twa_graph_ptr& a) sl(const_twa_graph_ptr a)
{
return sl(a, a->ap_vars());
}
twa_graph_ptr
sl2(const twa_graph_ptr& a)
{
return sl2(a, a->ap_vars());
}
twa_graph_ptr
sl(const const_twa_graph_ptr& a, bdd atomic_propositions)
{ {
// The result automaton uses numbered states. // The result automaton uses numbered states.
twa_graph_ptr res = make_twa_graph(a->get_dict()); twa_graph_ptr res = make_twa_graph(a->get_dict());
bdd atomic_propositions = a->ap_vars();
// We use the same BDD variables as the input. // We use the same BDD variables as the input.
res->copy_ap_of(a); res->copy_ap_of(a);
res->copy_acceptance_of(a); res->copy_acceptance_of(a);
...@@ -348,10 +335,9 @@ namespace spot ...@@ -348,10 +335,9 @@ namespace spot
} }
twa_graph_ptr twa_graph_ptr
sl2(twa_graph_ptr&& a, bdd atomic_propositions) sl2_inplace(twa_graph_ptr a)
{ {
if (atomic_propositions == bddfalse) bdd atomic_propositions = a->ap_vars();
atomic_propositions = a->ap_vars();
unsigned num_states = a->num_states(); unsigned num_states = a->num_states();
unsigned num_edges = a->num_edges(); unsigned num_edges = a->num_edges();
std::vector<bdd> selfloops(num_states, bddfalse); std::vector<bdd> selfloops(num_states, bddfalse);
...@@ -415,15 +401,13 @@ namespace spot ...@@ -415,15 +401,13 @@ namespace spot
} }
twa_graph_ptr twa_graph_ptr
sl2(const const_twa_graph_ptr& a, bdd atomic_propositions) sl2(const_twa_graph_ptr a)
{ {
return sl2(make_twa_graph(a, twa::prop_set::all()), return sl2_inplace(make_twa_graph(a, twa::prop_set::all()));
atomic_propositions);
} }
twa_graph_ptr twa_graph_ptr
closure(twa_graph_ptr&& a) closure_inplace(twa_graph_ptr a)
{ {
a->prop_keep({false, // state_based a->prop_keep({false, // state_based
false, // inherently_weak false, // inherently_weak
...@@ -506,33 +490,145 @@ namespace spot ...@@ -506,33 +490,145 @@ namespace spot
} }
twa_graph_ptr twa_graph_ptr
closure(const const_twa_graph_ptr& a) closure(const_twa_graph_ptr a)
{ {
return closure(make_twa_graph(a, twa::prop_set::all())); return closure_inplace(make_twa_graph(a, twa::prop_set::all()));
} }
// The stutter check algorithm to use can be overridden via an namespace
// environment variable.
static int default_stutter_check_algorithm()
{ {
static const char* stutter_check = getenv("SPOT_STUTTER_CHECK"); // The stutter check algorithm to use can be overridden via an
if (stutter_check) // environment variable.
{ static int default_stutter_check_algorithm()
char* endptr; {
long res = strtol(stutter_check, &endptr, 10); static const char* stutter_check = getenv("SPOT_STUTTER_CHECK");
if (*endptr || res < 0 || res > 9) if (stutter_check)
throw {
std::runtime_error("invalid value for SPOT_STUTTER_CHECK."); char* endptr;
return res; long res = strtol(stutter_check, &endptr, 10);
} if (*endptr || res < 0 || res > 9)
else throw
{ std::runtime_error("invalid value for SPOT_STUTTER_CHECK.");
return 8; // The best variant, according to our benchmarks. return res;
} }
else
{
return 8; // The best variant, according to our benchmarks.
}
}
}
namespace
{
// The own_f and own_nf tell us whether we can modify the aut_f
// and aut_nf automata inplace.
static bool do_si_check(const_twa_graph_ptr aut_f, bool own_f,
const_twa_graph_ptr aut_nf, bool own_nf,
int algo)
{
auto cl = [](const_twa_graph_ptr a, bool own) {
if (own)
return closure_inplace(std::const_pointer_cast<twa_graph>
(std::move(a)));
return closure(std::move(a));
};
auto sl_2 = [](const_twa_graph_ptr a, bool own) {
if (own)
return sl2_inplace(std::const_pointer_cast<twa_graph>(std::move(a)));
return sl2(std::move(a));
};
switch (algo)
{
case 1: // sl(aut_f) x sl(aut_nf)
return product(sl(std::move(aut_f)),
sl(std::move(aut_nf)))->is_empty();
case 2: // sl(cl(aut_f)) x aut_nf
return product(sl(cl(std::move(aut_f), own_f)),
std::move(aut_nf))->is_empty();
case 3: // (cl(sl(aut_f)) x aut_nf
return product(closure_inplace(sl(std::move(aut_f))),
std::move(aut_nf))->is_empty();
case 4: // sl2(aut_f) x sl2(aut_nf)
return product(sl_2(std::move(aut_f), own_f),
sl_2(std::move(aut_nf), own_nf))
->is_empty();
case 5: // sl2(cl(aut_f)) x aut_nf
return product(sl2_inplace(cl(std::move(aut_f), own_f)),
std::move(aut_nf))->is_empty();
case 6: // (cl(sl2(aut_f)) x aut_nf
return product(closure_inplace(sl_2(std::move(aut_f), own_f)),
std::move(aut_nf))->is_empty();
case 7: // on-the-fly sl(aut_f) x sl(aut_nf)
return otf_product(make_tgbasl(std::move(aut_f)),
make_tgbasl(std::move(aut_nf)))->is_empty();
case 8: // cl(aut_f) x cl(aut_nf)
return product(cl(std::move(aut_f), own_f),
cl(std::move(aut_nf), own_nf))->is_empty();
default:
throw std::runtime_error("is_stutter_invariant(): "
"invalid algorithm number");
SPOT_UNREACHABLE();
}
}
bool
is_stutter_invariant_aux(twa_graph_ptr aut_f,
bool own_f,
const_twa_graph_ptr aut_nf = nullptr,
int algo = 0)
{
trival si = aut_f->prop_stutter_invariant();
if (si.is_known())
return si.is_true();
if (aut_nf)
{
trival si_n = aut_nf->prop_stutter_invariant();
if (si_n.is_known())
{
bool res = si_n.is_true();
aut_f->prop_stutter_invariant(res);
return res;
}
}
if (algo == 0)
algo = default_stutter_check_algorithm();
bool own_nf = false;
if (!aut_nf)
{
twa_graph_ptr tmp = aut_f;
if (!is_deterministic(aut_f))
{
spot::postprocessor p;
p.set_type(spot::postprocessor::Generic);
p.set_pref(spot::postprocessor::Deterministic);
p.set_level(spot::postprocessor::Low);
tmp = p.run(aut_f);
}
aut_nf = dualize(std::move(tmp));
own_nf = true;
}
bool res = do_si_check(aut_f, own_f,
std::move(aut_nf), own_nf,
algo);
aut_f->prop_stutter_invariant(res);
return res;
}
} }
bool bool
is_stutter_invariant(formula f) is_stutter_invariant(twa_graph_ptr aut_f,
const_twa_graph_ptr aut_nf,
int algo)
{
return is_stutter_invariant_aux(aut_f, false, aut_nf, algo);
}
bool
is_stutter_invariant(formula f, twa_graph_ptr aut_f)
{ {
if (f.is_ltl_formula() && f.is_syntactic_stutter_invariant()) if (f.is_ltl_formula() && f.is_syntactic_stutter_invariant())
return true; return true;
...@@ -562,55 +658,18 @@ namespace spot ...@@ -562,55 +658,18 @@ namespace spot
} }
// Prepare for an automata-based check. // Prepare for an automata-based check.
translator trans; translator trans(aut_f ? aut_f->get_dict() : make_bdd_dict());
auto aut_f = trans.run(f); bool own_f = false;
auto aut_nf = trans.run(formula::Not(f)); if (!aut_f)
bdd aps = atomic_prop_collect_as_bdd(f, aut_f);
return is_stutter_invariant(std::move(aut_f), std::move(aut_nf), aps, algo);
}
bool
is_stutter_invariant(twa_graph_ptr&& aut_f,
twa_graph_ptr&& aut_nf, bdd aps, int algo)
{
if (algo == 0)
algo = default_stutter_check_algorithm();
switch (algo)
{ {
case 1: // sl(aut_f) x sl(aut_nf) aut_f = trans.run(f);
return product(sl(std::move(aut_f), aps), own_f = true;
sl(std::move(aut_nf), aps))->is_empty();
case 2: // sl(cl(aut_f)) x aut_nf
return product(sl(closure(std::move(aut_f)), aps),
std::move(aut_nf))->is_empty();
case 3: // (cl(sl(aut_f)) x aut_nf
return product(closure(sl(std::move(aut_f), aps)),
std::move(aut_nf))->is_empty();
case 4: // sl2(aut_f) x sl2(aut_nf)
return product(sl2(std::move(aut_f), aps),
sl2(std::move(aut_nf), aps))->is_empty();
case 5: // sl2(cl(aut_f)) x aut_nf
return product(sl2(closure(std::move(aut_f)), aps),
std::move(aut_nf))->is_empty();
case 6: // (cl(sl2(aut_f)) x aut_nf
return product(closure(sl2(std::move(aut_f), aps)),
std::move(aut_nf))->is_empty();
case 7: // on-the-fly sl(aut_f) x sl(aut_nf)
return otf_product(make_tgbasl(aut_f, aps),
make_tgbasl(aut_nf, aps))->is_empty();
case 8: // cl(aut_f) x cl(aut_nf)
return product(closure(std::move(aut_f)),
closure(std::move(aut_nf)))->is_empty();
default:
throw std::runtime_error("invalid algorithm number for "
"is_stutter_invariant()");
SPOT_UNREACHABLE();
} }
return is_stutter_invariant_aux(aut_f, own_f, trans.run(formula::Not(f)));
} }
trival trival
check_stutter_invariance(const twa_graph_ptr& aut, formula f, check_stutter_invariance(twa_graph_ptr aut, formula f,
bool do_not_determinize) bool do_not_determinize)
{ {
trival is_stut = aut->prop_stutter_invariant(); trival is_stut = aut->prop_stutter_invariant();
...@@ -619,33 +678,15 @@ namespace spot ...@@ -619,33 +678,15 @@ namespace spot
twa_graph_ptr neg = nullptr; twa_graph_ptr neg = nullptr;
if (f) if (f)
{ neg = translator(aut->get_dict()).run(formula::Not(f));
neg = translator(aut->get_dict()).run(formula::Not(f)); else if (!is_deterministic(aut) && do_not_determinize)
} return trival::maybe();
else
{
twa_graph_ptr tmp = aut;
if (!is_deterministic(aut))
{
if (do_not_determinize)
return trival::maybe();
spot::postprocessor p;
p.set_type(spot::postprocessor::Generic);
p.set_pref(spot::postprocessor::Deterministic);
p.set_level(spot::postprocessor::Low);
tmp = p.run(aut);
}
neg = dualize(tmp);
}
is_stut = is_stutter_invariant(make_twa_graph(aut, twa::prop_set::all()), return is_stutter_invariant(aut, std::move(neg));
std::move(neg), aut->ap_vars());
aut->prop_stutter_invariant(is_stut);
return is_stut;
} }
std::vector<