rename src/ as spot/ and use include <spot/...>
* NEWS: Mention the change. * src/: Rename as ... * spot/: ... this, adjust all headers to include <spot/...> instead of "...", and adjust all Makefile.am to search headers from the top-level directory. * HACKING: Add conventions about #include. * spot/sanity/style.test: Add a few more grep to catch cases that do not follow these conventions. * .gitignore, Makefile.am, README, bench/stutter/Makefile.am, bench/stutter/stutter_invariance_formulas.cc, bench/stutter/stutter_invariance_randomgraph.cc, configure.ac, debian/rules, doc/Doxyfile.in, doc/Makefile.am, doc/org/.dir-locals.el.in, doc/org/g++wrap.in, doc/org/init.el.in, doc/org/tut01.org, doc/org/tut02.org, doc/org/tut03.org, doc/org/tut10.org, doc/org/tut20.org, doc/org/tut21.org, doc/org/tut22.org, doc/org/tut30.org, iface/ltsmin/Makefile.am, iface/ltsmin/kripke.test, iface/ltsmin/ltsmin.cc, iface/ltsmin/ltsmin.hh, iface/ltsmin/modelcheck.cc, wrap/python/Makefile.am, wrap/python/ajax/spotcgi.in, wrap/python/spot_impl.i, wrap/python/tests/ltl2tgba.py, wrap/python/tests/randgen.py, wrap/python/tests/run.in: Adjust.
This commit is contained in:
parent
1fddfe60ec
commit
f120dd3206
529 changed files with 1308 additions and 1262 deletions
43
spot/taalgos/Makefile.am
Normal file
43
spot/taalgos/Makefile.am
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
## Copyright (C) 2010, 2012, 2013, 2015 Laboratoire de Recherche et
|
||||
## Développement de l'Epita (LRDE).
|
||||
##
|
||||
## 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/>.
|
||||
|
||||
|
||||
AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) -I.. $(BUDDY_CPPFLAGS)
|
||||
AM_CXXFLAGS = $(WARNING_CXXFLAGS)
|
||||
|
||||
taalgosdir = $(pkgincludedir)/taalgos
|
||||
|
||||
taalgos_HEADERS = \
|
||||
tgba2ta.hh \
|
||||
dot.hh \
|
||||
reachiter.hh \
|
||||
stats.hh \
|
||||
statessetbuilder.hh \
|
||||
minimize.hh \
|
||||
emptinessta.hh
|
||||
|
||||
noinst_LTLIBRARIES = libtaalgos.la
|
||||
libtaalgos_la_SOURCES = \
|
||||
tgba2ta.cc \
|
||||
dot.cc \
|
||||
reachiter.cc \
|
||||
stats.cc \
|
||||
statessetbuilder.cc \
|
||||
minimize.cc \
|
||||
emptinessta.cc
|
||||
241
spot/taalgos/dot.cc
Normal file
241
spot/taalgos/dot.cc
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2012, 2014, 2015 Laboratoire de Recherche et
|
||||
// Developpement de l Epita (LRDE).
|
||||
//
|
||||
// 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 <ostream>
|
||||
#include <spot/taalgos/dot.hh>
|
||||
#include <spot/twa/bddprint.hh>
|
||||
#include <spot/taalgos/reachiter.hh>
|
||||
#include <spot/misc/escape.hh>
|
||||
#include <spot/misc/bareword.hh>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class dotty_bfs : public ta_reachable_iterator_breadth_first
|
||||
{
|
||||
void
|
||||
parse_opts(const char* options)
|
||||
{
|
||||
const char* orig = options;
|
||||
while (char c = *options++)
|
||||
switch (c)
|
||||
{
|
||||
case '.':
|
||||
{
|
||||
// Copy the value in a string, so future calls to
|
||||
// parse_opts do not fail if the environment has
|
||||
// changed. (This matters particularly in an ipython
|
||||
// notebook, where it is tempting to redefine
|
||||
// SPOT_DOTDEFAULT.)
|
||||
static std::string def = []()
|
||||
{
|
||||
auto s = getenv("SPOT_DOTDEFAULT");
|
||||
return s ? s : "";
|
||||
}();
|
||||
// Prevent infinite recursions...
|
||||
if (orig == def.c_str())
|
||||
throw std::runtime_error
|
||||
(std::string("SPOT_DOTDEFAULT should not contain '.'"));
|
||||
if (!def.empty())
|
||||
parse_opts(def.c_str());
|
||||
break;
|
||||
}
|
||||
case 'A':
|
||||
opt_hide_sets_ = true;
|
||||
break;
|
||||
case 'c':
|
||||
opt_circles_ = true;
|
||||
break;
|
||||
case 'h':
|
||||
opt_horizontal_ = true;
|
||||
break;
|
||||
case 'f':
|
||||
if (*options != '(')
|
||||
throw std::runtime_error
|
||||
(std::string("invalid font specification for dotty()"));
|
||||
{
|
||||
auto* end = strchr(++options, ')');
|
||||
if (!end)
|
||||
throw std::runtime_error
|
||||
(std::string("invalid font specification for dotty()"));
|
||||
opt_font_ = std::string(options, end - options);
|
||||
options = end + 1;
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
opt_horizontal_ = false;
|
||||
break;
|
||||
case '1':
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'B':
|
||||
case 'e':
|
||||
case 'n':
|
||||
case 'N':
|
||||
case 'o':
|
||||
case 'r':
|
||||
case 'R':
|
||||
case 's':
|
||||
case 't':
|
||||
// All these options are implemented by dotty() on TGBA,
|
||||
// but are not implemented here. We simply ignore them,
|
||||
// because raising an exception if they are in
|
||||
// SPOT_DEFAULT would be annoying.
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error
|
||||
(std::string("unknown option for dotty(): ") + c);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
dotty_bfs(std::ostream& os, const const_ta_ptr& a,
|
||||
const char* opt) :
|
||||
ta_reachable_iterator_breadth_first(a), os_(os)
|
||||
{
|
||||
parse_opts(opt ? opt : ".");
|
||||
}
|
||||
|
||||
void
|
||||
start()
|
||||
{
|
||||
os_ << "digraph G {\n";
|
||||
|
||||
if (opt_horizontal_)
|
||||
os_ << " rankdir=LR\n";
|
||||
if (opt_circles_)
|
||||
os_ << " node [shape=\"circle\"]\n";
|
||||
if (!opt_font_.empty())
|
||||
os_ << " fontname=\"" << opt_font_
|
||||
<< "\"\n node [fontname=\"" << opt_font_
|
||||
<< "\"]\n edge [fontname=\"" << opt_font_
|
||||
<< "\"]\n";
|
||||
|
||||
// Always copy the environment variable into a static string,
|
||||
// so that we (1) look it up once, but (2) won't crash if the
|
||||
// environment is changed.
|
||||
static std::string extra = []()
|
||||
{
|
||||
auto s = getenv("SPOT_DOTEXTRA");
|
||||
return s ? s : "";
|
||||
}();
|
||||
// Any extra text passed in the SPOT_DOTEXTRA environment
|
||||
// variable should be output at the end of the "header", so
|
||||
// that our setup can be overridden.
|
||||
if (!extra.empty())
|
||||
os_ << " " << extra << '\n';
|
||||
|
||||
artificial_initial_state_ = t_automata_->get_artificial_initial_state();
|
||||
|
||||
ta::const_states_set_t init_states_set;
|
||||
|
||||
if (artificial_initial_state_)
|
||||
{
|
||||
init_states_set.insert(artificial_initial_state_);
|
||||
os_ << " 0 [label=\"\", style=invis, height=0]\n 0 -> 1\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
int n = 0;
|
||||
init_states_set = t_automata_->get_initial_states_set();
|
||||
for (auto s: init_states_set)
|
||||
{
|
||||
bdd init_condition = t_automata_->get_state_condition(s);
|
||||
std::string label = bdd_format_formula(t_automata_->get_dict(),
|
||||
init_condition);
|
||||
++n;
|
||||
os_ << " " << -n << " [label=\"\", style=invis, height=0]\n "
|
||||
<< -n << " -> " << n << " [label=\"" << label << "\"]\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
end()
|
||||
{
|
||||
os_ << '}' << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
process_state(const state* s, int n)
|
||||
{
|
||||
|
||||
std::string style;
|
||||
if (t_automata_->is_accepting_state(s))
|
||||
style = ",peripheries=2";
|
||||
|
||||
if (t_automata_->is_livelock_accepting_state(s))
|
||||
style += ",shape=box";
|
||||
|
||||
os_ << " " << n << " [label=";
|
||||
if (s == artificial_initial_state_)
|
||||
os_ << "init";
|
||||
else
|
||||
os_ << quote_unless_bare_word(t_automata_->format_state(s));
|
||||
os_ << style << "]\n";
|
||||
}
|
||||
|
||||
void
|
||||
process_link(int in, int out, const ta_succ_iterator* si)
|
||||
{
|
||||
bdd_dict_ptr d = t_automata_->get_dict();
|
||||
std::string label =
|
||||
((in == 1 && artificial_initial_state_)
|
||||
? bdd_format_formula(d, si->cond())
|
||||
: bdd_format_accset(d, si->cond()));
|
||||
|
||||
if (label.empty())
|
||||
label = "{}";
|
||||
|
||||
if (!opt_hide_sets_)
|
||||
{
|
||||
label += "\n";
|
||||
label += t_automata_->acc().
|
||||
format(si->acc());
|
||||
}
|
||||
|
||||
os_ << " " << in << " -> " << out << " [label=\"";
|
||||
escape_str(os_, label);
|
||||
os_ << "\"]\n";
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream& os_;
|
||||
const spot::state* artificial_initial_state_;
|
||||
|
||||
bool opt_horizontal_ = true;
|
||||
bool opt_circles_ = false;
|
||||
bool opt_hide_sets_ = false;
|
||||
std::string opt_font_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
print_dot(std::ostream& os, const const_ta_ptr& a, const char* opt)
|
||||
{
|
||||
dotty_bfs d(os, a, opt);
|
||||
d.run();
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
||||
30
spot/taalgos/dot.hh
Normal file
30
spot/taalgos/dot.hh
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/ta/ta.hh>
|
||||
#include <iosfwd>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
SPOT_API std::ostream&
|
||||
print_dot(std::ostream& os, const const_ta_ptr& a,
|
||||
const char* opt = nullptr);
|
||||
}
|
||||
626
spot/taalgos/emptinessta.cc
Normal file
626
spot/taalgos/emptinessta.cc
Normal file
|
|
@ -0,0 +1,626 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 Laboratoire de
|
||||
// Recherche et Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
//#define TRACE
|
||||
|
||||
#include <iostream>
|
||||
#ifdef TRACE
|
||||
#define trace std::clog
|
||||
#else
|
||||
#define trace while (0) std::clog
|
||||
#endif
|
||||
|
||||
#include <spot/taalgos/emptinessta.hh>
|
||||
#include <spot/misc/memusage.hh>
|
||||
#include <cstdlib>
|
||||
#include <spot/twa/bddprint.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
ta_check::ta_check(const const_ta_product_ptr& a, option_map o) :
|
||||
a_(a), o_(o)
|
||||
{
|
||||
is_full_2_pass_ = o.get("is_full_2_pass", 0);
|
||||
}
|
||||
|
||||
ta_check::~ta_check()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ta_check::check(bool disable_second_pass,
|
||||
bool disable_heuristic_for_livelock_detection)
|
||||
{
|
||||
|
||||
// We use five main data in this algorithm:
|
||||
|
||||
// * scc: (attribute) a stack of strongly connected components (SCC)
|
||||
|
||||
// * arc, a stack of acceptance conditions between each of these SCC,
|
||||
std::stack<acc_cond::mark_t> arc;
|
||||
|
||||
// * h: a hash of all visited nodes, with their order,
|
||||
// (it is called "Hash" in Couvreur's paper)
|
||||
hash_type h;
|
||||
|
||||
// * num: the number of visited nodes. Used to set the order of each
|
||||
// visited node,
|
||||
int num = 1;
|
||||
|
||||
// * todo: the depth-first search stack. This holds pairs of the
|
||||
// form (STATE, ITERATOR) where ITERATOR is a ta_succ_iterator_product
|
||||
// over the successors of STATE. In our use, ITERATOR should
|
||||
// always be freed when TODO is popped, but STATE should not because
|
||||
// it is also used as a key in H.
|
||||
std::stack<pair_state_iter> todo;
|
||||
|
||||
trace
|
||||
<< "PASS 1" << std::endl;
|
||||
|
||||
std::unordered_map<const state*,
|
||||
std::set<const state*, state_ptr_less_than>,
|
||||
state_ptr_hash, state_ptr_equal> liveset;
|
||||
|
||||
std::stack<spot::state*> livelock_roots;
|
||||
|
||||
bool livelock_acceptance_states_not_found = true;
|
||||
|
||||
bool activate_heuristic = !disable_heuristic_for_livelock_detection
|
||||
&& (is_full_2_pass_ == disable_second_pass);
|
||||
|
||||
// Setup depth-first search from initial states.
|
||||
auto& ta_ = a_->get_ta();
|
||||
auto& kripke_ = a_->get_kripke();
|
||||
auto kripke_init_state = kripke_->get_init_state();
|
||||
bdd kripke_init_state_condition = kripke_->state_condition(
|
||||
kripke_init_state);
|
||||
|
||||
auto artificial_initial_state = ta_->get_artificial_initial_state();
|
||||
|
||||
ta_succ_iterator* ta_init_it_ = ta_->succ_iter(artificial_initial_state,
|
||||
kripke_init_state_condition);
|
||||
kripke_init_state->destroy();
|
||||
for (ta_init_it_->first(); !ta_init_it_->done(); ta_init_it_->next())
|
||||
{
|
||||
|
||||
state_ta_product* init = new state_ta_product(
|
||||
(ta_init_it_->dst()), kripke_init_state->clone());
|
||||
|
||||
if (!h.emplace(init, num + 1).second)
|
||||
{
|
||||
init->destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
scc.push(++num);
|
||||
arc.push(0U);
|
||||
|
||||
ta_succ_iterator_product* iter = a_->succ_iter(init);
|
||||
iter->first();
|
||||
todo.emplace(init, iter);
|
||||
|
||||
inc_depth();
|
||||
|
||||
//push potential root of live-lock accepting cycle
|
||||
if (activate_heuristic && a_->is_livelock_accepting_state(init))
|
||||
livelock_roots.push(init);
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
auto curr = todo.top().first;
|
||||
|
||||
// We are looking at the next successor in SUCC.
|
||||
ta_succ_iterator_product* succ = todo.top().second;
|
||||
|
||||
// If there is no more successor, backtrack.
|
||||
if (succ->done())
|
||||
{
|
||||
// We have explored all successors of state CURR.
|
||||
|
||||
|
||||
// Backtrack TODO.
|
||||
todo.pop();
|
||||
dec_depth();
|
||||
trace << "PASS 1 : backtrack\n";
|
||||
|
||||
if (a_->is_livelock_accepting_state(curr)
|
||||
&& !a_->is_accepting_state(curr))
|
||||
{
|
||||
livelock_acceptance_states_not_found = false;
|
||||
trace << "PASS 1 : livelock accepting state found\n";
|
||||
}
|
||||
|
||||
// fill rem with any component removed,
|
||||
auto i = h.find(curr);
|
||||
assert(i != h.end());
|
||||
|
||||
scc.rem().push_front(curr);
|
||||
inc_depth();
|
||||
|
||||
// set the h value of the Backtracked state to negative value.
|
||||
i->second = -std::abs(i->second);
|
||||
|
||||
// Backtrack livelock_roots.
|
||||
if (activate_heuristic && !livelock_roots.empty()
|
||||
&& !livelock_roots.top()->compare(curr))
|
||||
livelock_roots.pop();
|
||||
|
||||
// When backtracking the root of an SSCC, we must also
|
||||
// remove that SSCC from the ROOT stacks. We must
|
||||
// discard from H all reachable states from this SSCC.
|
||||
assert(!scc.empty());
|
||||
if (scc.top().index == std::abs(i->second))
|
||||
{
|
||||
// removing states
|
||||
for (auto j: scc.rem())
|
||||
h[j] = -1;
|
||||
dec_depth(scc.rem().size());
|
||||
scc.pop();
|
||||
assert(!arc.empty());
|
||||
arc.pop();
|
||||
|
||||
}
|
||||
|
||||
delete succ;
|
||||
// Do not delete CURR: it is a key in H.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a successor to look at.
|
||||
inc_transitions();
|
||||
trace << "PASS 1: transition\n";
|
||||
// Fetch the values destination state we are interested in...
|
||||
state* dest = succ->dst();
|
||||
|
||||
auto acc_cond = succ->acc();
|
||||
|
||||
bool curr_is_livelock_hole_state_in_ta_component =
|
||||
(a_->is_hole_state_in_ta_component(curr))
|
||||
&& a_->is_livelock_accepting_state(curr);
|
||||
|
||||
// May be Buchi accepting scc or livelock accepting scc
|
||||
// (contains a livelock accepting state that have no
|
||||
// successors in TA).
|
||||
scc.top().is_accepting = (a_->is_accepting_state(curr)
|
||||
&& (!succ->is_stuttering_transition()
|
||||
|| a_->is_livelock_accepting_state(curr)))
|
||||
|| curr_is_livelock_hole_state_in_ta_component;
|
||||
|
||||
bool is_stuttering_transition = succ->is_stuttering_transition();
|
||||
|
||||
// ... and point the iterator to the next successor, for
|
||||
// the next iteration.
|
||||
succ->next();
|
||||
// We do not need SUCC from now on.
|
||||
|
||||
// Are we going to a new state?
|
||||
auto p = h.emplace(dest, num + 1);
|
||||
if (p.second)
|
||||
{
|
||||
// Number it, stack it, and register its successors
|
||||
// for later processing.
|
||||
scc.push(++num);
|
||||
arc.push(acc_cond);
|
||||
|
||||
ta_succ_iterator_product* iter = a_->succ_iter(dest);
|
||||
iter->first();
|
||||
todo.emplace(dest, iter);
|
||||
inc_depth();
|
||||
|
||||
//push potential root of live-lock accepting cycle
|
||||
if (activate_heuristic && a_->is_livelock_accepting_state(dest)
|
||||
&& !is_stuttering_transition)
|
||||
livelock_roots.push(dest);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have reached a dead component, ignore it.
|
||||
if (p.first->second == -1)
|
||||
continue;
|
||||
|
||||
// Now this is the most interesting case. We have reached a
|
||||
// state S1 which is already part of a non-dead SSCC. Any such
|
||||
// non-dead SSCC has necessarily been crossed by our path to
|
||||
// this state: there is a state S2 in our path which belongs
|
||||
// to this SSCC too. We are going to merge all states between
|
||||
// this S1 and S2 into this SSCC.
|
||||
//
|
||||
// This merge is easy to do because the order of the SSCC in
|
||||
// ROOT is ascending: we just have to merge all SSCCs from the
|
||||
// top of ROOT that have an index greater to the one of
|
||||
// the SSCC of S2 (called the "threshold").
|
||||
int threshold = std::abs(p.first->second);
|
||||
std::list<const state*> rem;
|
||||
bool acc = false;
|
||||
|
||||
trace << "***PASS 1: CYCLE***\n";
|
||||
|
||||
while (threshold < scc.top().index)
|
||||
{
|
||||
assert(!scc.empty());
|
||||
assert(!arc.empty());
|
||||
|
||||
acc |= scc.top().is_accepting;
|
||||
acc_cond |= scc.top().condition;
|
||||
acc_cond |= arc.top();
|
||||
|
||||
rem.splice(rem.end(), scc.rem());
|
||||
scc.pop();
|
||||
arc.pop();
|
||||
|
||||
}
|
||||
|
||||
// Note that we do not always have
|
||||
// threshold == scc.top().index
|
||||
// after this loop, the SSCC whose index is threshold might have
|
||||
// been merged with a lower SSCC.
|
||||
|
||||
// Accumulate all acceptance conditions into the merged SSCC.
|
||||
scc.top().is_accepting |= acc;
|
||||
scc.top().condition |= acc_cond;
|
||||
|
||||
scc.rem().splice(scc.rem().end(), rem);
|
||||
bool is_accepting_sscc = scc.top().is_accepting
|
||||
|| a_->acc().accepting(scc.top().condition);
|
||||
|
||||
if (is_accepting_sscc)
|
||||
{
|
||||
trace
|
||||
<< "PASS 1: SUCCESS: a_->is_livelock_accepting_state(curr): "
|
||||
<< a_->is_livelock_accepting_state(curr) << '\n';
|
||||
trace
|
||||
<< "PASS 1: scc.top().condition : "
|
||||
<< scc.top().condition << '\n';
|
||||
trace
|
||||
<< "PASS 1: a_->acc().all_sets() : "
|
||||
<< (a_->acc().all_sets()) << '\n';
|
||||
trace
|
||||
<< ("PASS 1 CYCLE and accepting? ")
|
||||
<< a_->acc().accepting(scc.top().condition)
|
||||
<< std::endl;
|
||||
clear(h, todo, ta_init_it_);
|
||||
return true;
|
||||
}
|
||||
|
||||
//ADDLINKS
|
||||
if (activate_heuristic && a_->is_livelock_accepting_state(curr)
|
||||
&& is_stuttering_transition)
|
||||
{
|
||||
trace << "PASS 1: heuristic livelock detection \n";
|
||||
const state* dest = p.first->first;
|
||||
std::set<const state*, state_ptr_less_than> liveset_dest =
|
||||
liveset[dest];
|
||||
|
||||
std::set<const state*, state_ptr_less_than> liveset_curr =
|
||||
liveset[curr];
|
||||
|
||||
int h_livelock_root = 0;
|
||||
if (!livelock_roots.empty())
|
||||
h_livelock_root = h[livelock_roots.top()];
|
||||
|
||||
if (heuristic_livelock_detection(dest, h, h_livelock_root,
|
||||
liveset_curr))
|
||||
{
|
||||
clear(h, todo, ta_init_it_);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const state* succ: liveset_dest)
|
||||
if (heuristic_livelock_detection(succ, h, h_livelock_root,
|
||||
liveset_curr))
|
||||
{
|
||||
clear(h, todo, ta_init_it_);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
clear(h, todo, ta_init_it_);
|
||||
|
||||
if (disable_second_pass || livelock_acceptance_states_not_found)
|
||||
return false;
|
||||
|
||||
return livelock_detection(a_);
|
||||
}
|
||||
|
||||
bool
|
||||
ta_check::heuristic_livelock_detection(const state * u,
|
||||
hash_type& h, int h_livelock_root, std::set<const state*,
|
||||
state_ptr_less_than> liveset_curr)
|
||||
{
|
||||
int hu = h[u];
|
||||
|
||||
if (hu > 0)
|
||||
{
|
||||
|
||||
if (hu >= h_livelock_root)
|
||||
{
|
||||
trace << "PASS 1: heuristic livelock detection SUCCESS\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
liveset_curr.insert(u);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ta_check::livelock_detection(const const_ta_product_ptr& t)
|
||||
{
|
||||
// We use five main data in this algorithm:
|
||||
|
||||
// * sscc: a stack of strongly stuttering-connected components (SSCC)
|
||||
|
||||
|
||||
// * h: a hash of all visited nodes, with their order,
|
||||
// (it is called "Hash" in Couvreur's paper)
|
||||
hash_type h;
|
||||
|
||||
// * num: the number of visited nodes. Used to set the order of each
|
||||
// visited node,
|
||||
|
||||
trace
|
||||
<< "PASS 2" << std::endl;
|
||||
|
||||
int num = 0;
|
||||
|
||||
// * todo: the depth-first search stack. This holds pairs of the
|
||||
// form (STATE, ITERATOR) where ITERATOR is a twa_succ_iterator
|
||||
// over the successors of STATE. In our use, ITERATOR should
|
||||
// always be freed when TODO is popped, but STATE should not because
|
||||
// it is also used as a key in H.
|
||||
std::stack<pair_state_iter> todo;
|
||||
|
||||
// * init: the set of the depth-first search initial states
|
||||
std::queue<const spot::state*> ta_init_it_;
|
||||
|
||||
auto init_states_set = a_->get_initial_states_set();
|
||||
for (auto init_state: init_states_set)
|
||||
ta_init_it_.push(init_state);
|
||||
|
||||
while (!ta_init_it_.empty())
|
||||
{
|
||||
// Setup depth-first search from initial states.
|
||||
{
|
||||
auto init = ta_init_it_.front();
|
||||
ta_init_it_.pop();
|
||||
|
||||
if (!h.emplace(init, num + 1).second)
|
||||
{
|
||||
init->destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
sscc.push(num);
|
||||
sscc.top().is_accepting = t->is_livelock_accepting_state(init);
|
||||
ta_succ_iterator_product* iter = t->succ_iter(init);
|
||||
iter->first();
|
||||
todo.emplace(init, iter);
|
||||
inc_depth();
|
||||
}
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
auto curr = todo.top().first;
|
||||
|
||||
// We are looking at the next successor in SUCC.
|
||||
ta_succ_iterator_product* succ = todo.top().second;
|
||||
|
||||
// If there is no more successor, backtrack.
|
||||
if (succ->done())
|
||||
{
|
||||
// We have explored all successors of state CURR.
|
||||
|
||||
// Backtrack TODO.
|
||||
todo.pop();
|
||||
dec_depth();
|
||||
trace << "PASS 2 : backtrack\n";
|
||||
|
||||
// fill rem with any component removed,
|
||||
auto i = h.find(curr);
|
||||
assert(i != h.end());
|
||||
|
||||
sscc.rem().push_front(curr);
|
||||
inc_depth();
|
||||
|
||||
// When backtracking the root of an SSCC, we must also
|
||||
// remove that SSCC from the ROOT stacks. We must
|
||||
// discard from H all reachable states from this SSCC.
|
||||
assert(!sscc.empty());
|
||||
if (sscc.top().index == i->second)
|
||||
{
|
||||
// removing states
|
||||
for (auto j: sscc.rem())
|
||||
h[j] = -1;
|
||||
dec_depth(sscc.rem().size());
|
||||
sscc.pop();
|
||||
}
|
||||
|
||||
delete succ;
|
||||
// Do not delete CURR: it is a key in H.
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a successor to look at.
|
||||
inc_transitions();
|
||||
trace << "PASS 2 : transition\n";
|
||||
// Fetch the values destination state we are interested in...
|
||||
state* dest = succ->dst();
|
||||
|
||||
bool is_stuttering_transition = succ->is_stuttering_transition();
|
||||
// ... and point the iterator to the next successor, for
|
||||
// the next iteration.
|
||||
succ->next();
|
||||
// We do not need SUCC from now on.
|
||||
|
||||
auto i = h.find(dest);
|
||||
|
||||
// Is this a new state?
|
||||
if (i == h.end())
|
||||
{
|
||||
|
||||
// Are we going to a new state through a stuttering transition?
|
||||
|
||||
if (!is_stuttering_transition)
|
||||
{
|
||||
ta_init_it_.push(dest);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Number it, stack it, and register its successors
|
||||
// for later processing.
|
||||
h[dest] = ++num;
|
||||
sscc.push(num);
|
||||
sscc.top().is_accepting = t->is_livelock_accepting_state(dest);
|
||||
|
||||
ta_succ_iterator_product* iter = t->succ_iter(dest);
|
||||
iter->first();
|
||||
todo.emplace(dest, iter);
|
||||
inc_depth();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest->destroy();
|
||||
}
|
||||
|
||||
// If we have reached a dead component, ignore it.
|
||||
if (i->second == -1)
|
||||
continue;
|
||||
|
||||
//self loop state
|
||||
if (!curr->compare(i->first))
|
||||
if (t->is_livelock_accepting_state(curr))
|
||||
{
|
||||
clear(h, todo, ta_init_it_);
|
||||
trace << "PASS 2: SUCCESS\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now this is the most interesting case. We have reached a
|
||||
// state S1 which is already part of a non-dead SSCC. Any such
|
||||
// non-dead SSCC has necessarily been crossed by our path to
|
||||
// this state: there is a state S2 in our path which belongs
|
||||
// to this SSCC too. We are going to merge all states between
|
||||
// this S1 and S2 into this SSCC.
|
||||
//
|
||||
// This merge is easy to do because the order of the SSCC in
|
||||
// ROOT is ascending: we just have to merge all SSCCs from the
|
||||
// top of ROOT that have an index greater to the one of
|
||||
// the SSCC of S2 (called the "threshold").
|
||||
int threshold = i->second;
|
||||
std::list<const state*> rem;
|
||||
bool acc = false;
|
||||
|
||||
while (threshold < sscc.top().index)
|
||||
{
|
||||
assert(!sscc.empty());
|
||||
|
||||
acc |= sscc.top().is_accepting;
|
||||
|
||||
rem.splice(rem.end(), sscc.rem());
|
||||
sscc.pop();
|
||||
|
||||
}
|
||||
// Note that we do not always have
|
||||
// threshold == sscc.top().index
|
||||
// after this loop, the SSCC whose index is threshold might have
|
||||
// been merged with a lower SSCC.
|
||||
|
||||
// Accumulate all acceptance conditions into the merged SSCC.
|
||||
sscc.top().is_accepting |= acc;
|
||||
|
||||
sscc.rem().splice(sscc.rem().end(), rem);
|
||||
if (sscc.top().is_accepting)
|
||||
{
|
||||
clear(h, todo, ta_init_it_);
|
||||
trace
|
||||
<< "PASS 2: SUCCESS" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
clear(h, todo, ta_init_it_);
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ta_check::clear(hash_type& h, std::stack<pair_state_iter> todo,
|
||||
std::queue<const spot::state*> init_states)
|
||||
{
|
||||
|
||||
set_states(states() + h.size());
|
||||
|
||||
while (!init_states.empty())
|
||||
{
|
||||
a_->free_state(init_states.front());
|
||||
init_states.pop();
|
||||
}
|
||||
|
||||
// Release all iterators in TODO.
|
||||
while (!todo.empty())
|
||||
{
|
||||
delete todo.top().second;
|
||||
todo.pop();
|
||||
dec_depth();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ta_check::clear(hash_type& h, std::stack<pair_state_iter> todo,
|
||||
spot::ta_succ_iterator* init_states_it)
|
||||
{
|
||||
|
||||
set_states(states() + h.size());
|
||||
|
||||
delete init_states_it;
|
||||
|
||||
// Release all iterators in TODO.
|
||||
while (!todo.empty())
|
||||
{
|
||||
delete todo.top().second;
|
||||
todo.pop();
|
||||
dec_depth();
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
ta_check::print_stats(std::ostream& os) const
|
||||
{
|
||||
// ecs_->print_stats(os);
|
||||
os << states() << " unique states visited" << std::endl;
|
||||
|
||||
//TODO sscc;
|
||||
os << scc.size() << " strongly connected components in search stack"
|
||||
<< std::endl;
|
||||
os << transitions() << " transitions explored" << std::endl;
|
||||
os << max_depth() << " items max in DFS search stack" << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
}
|
||||
162
spot/taalgos/emptinessta.hh
Normal file
162
spot/taalgos/emptinessta.hh
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2012, 2013, 2014 Laboratoire de Recherche et
|
||||
// Dévelopment de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 2004, 2005, 2006 Laboratoire d'Informatique de
|
||||
// Paris 6 (LIP6), département Systèmes Répartis Coopératifs (SRC),
|
||||
// Université Pierre et Marie Curie.
|
||||
//
|
||||
// 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/ta/taproduct.hh>
|
||||
#include <spot/misc/optionmap.hh>
|
||||
#include <spot/twaalgos/emptiness_stats.hh>
|
||||
#include <stack>
|
||||
#include <queue>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef std::pair<const spot::state*,
|
||||
ta_succ_iterator_product*> pair_state_iter;
|
||||
}
|
||||
|
||||
/// \addtogroup ta_emptiness_check Emptiness-checks
|
||||
/// \ingroup ta_algorithms
|
||||
///
|
||||
/// \brief Check whether the language of a product (spot::ta_product) between
|
||||
/// a Kripke structure and a TA is empty. It works also for the product
|
||||
/// using Generalized TA (GTA and SGTA).
|
||||
///
|
||||
/// you should call spot::ta_check::check() to check the product automaton.
|
||||
/// If spot::ta_check::check() returns false, then the product automaton
|
||||
/// was found empty. Otherwise the automaton accepts some run.
|
||||
///
|
||||
/// This is based on the following paper.
|
||||
/** \verbatim
|
||||
@InProceedings{ geldenhuys.06.spin,
|
||||
author = {Jaco Geldenhuys and Henri Hansen},
|
||||
title = {Larger Automata and Less Work for {LTL} Model Checking},
|
||||
booktitle = {Proceedings of the 13th International SPIN Workshop
|
||||
(SPIN'06)},
|
||||
year = {2006},
|
||||
pages = {53--70},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
volume = {3925},
|
||||
publisher = {Springer}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// the implementation of spot::ta_check::check() is inspired from the
|
||||
/// two-pass algorithm of the paper above:
|
||||
/// - the fist-pass detect all Buchi-accepting cycles and includes
|
||||
/// the heuristic proposed in the paper to detect some
|
||||
/// livelock-accepting cycles.
|
||||
/// - the second-pass detect all livelock-accepting cycles.
|
||||
/// In addition, we add some optimizations to the fist pass:
|
||||
/// 1- Detection of all cycles containing a least
|
||||
/// one state that is both livelock and Buchi accepting states
|
||||
/// 2- Detection of all livelock-accepting cycles containing a least
|
||||
/// one state (k,t) such as its "TA component" t is a livelock-accepting
|
||||
/// state that has no successors in the TA automaton.
|
||||
///
|
||||
/// The implementation of the algorithm of each pass is a SCC-based algorithm
|
||||
/// inspired from spot::gtec.hh.
|
||||
/// @{
|
||||
|
||||
/// \brief An implementation of the emptiness-check algorithm for a product
|
||||
/// between a TA and a Kripke structure
|
||||
///
|
||||
/// See the paper cited above.
|
||||
class SPOT_API ta_check : public ec_statistics
|
||||
{
|
||||
typedef std::unordered_map<const state*, int,
|
||||
state_ptr_hash, state_ptr_equal> hash_type;
|
||||
public:
|
||||
ta_check(const const_ta_product_ptr& a, option_map o = option_map());
|
||||
virtual
|
||||
~ta_check();
|
||||
|
||||
/// \brief Check whether the TA product automaton contains an accepting run:
|
||||
/// it detects the two kinds of accepting runs: Buchi-accepting runs
|
||||
/// and livelock-accepting runs. This emptiness check algorithm can also
|
||||
/// check a product using the generalized form of TA.
|
||||
///
|
||||
/// Return false if the product automaton accepts no run, otherwise true
|
||||
///
|
||||
/// \param disable_second_pass is used to disable the second pass when
|
||||
/// when it is not necessary, for example when all the livelock-accepting
|
||||
/// states of the TA automaton have no successors, we call this kind of
|
||||
/// TA as STA (Single-pass Testing Automata)
|
||||
/// (see spot::tgba2ta::add_artificial_livelock_accepting_state() for an
|
||||
/// automatic transformation of any TA automaton into STA automaton
|
||||
///
|
||||
/// \param disable_heuristic_for_livelock_detection disable the heuristic
|
||||
/// used in the first pass to detect livelock-accepting runs,
|
||||
/// this heuristic is described in the paper cited above
|
||||
virtual bool
|
||||
check(bool disable_second_pass = false,
|
||||
bool disable_heuristic_for_livelock_detection = false);
|
||||
|
||||
/// \brief Check whether the product automaton contains
|
||||
/// a livelock-accepting run
|
||||
/// Return false if the product automaton accepts no livelock-accepting run,
|
||||
/// otherwise true
|
||||
virtual bool
|
||||
livelock_detection(const const_ta_product_ptr& t);
|
||||
|
||||
/// Print statistics, if any.
|
||||
virtual std::ostream&
|
||||
print_stats(std::ostream& os) const;
|
||||
|
||||
protected:
|
||||
void
|
||||
clear(hash_type& h, std::stack<pair_state_iter> todo, std::queue<
|
||||
const spot::state*> init_set);
|
||||
|
||||
void
|
||||
clear(hash_type& h, std::stack<pair_state_iter> todo,
|
||||
spot::ta_succ_iterator* init_states_it);
|
||||
|
||||
/// the heuristic for livelock-accepting runs detection, it's described
|
||||
/// in the paper cited above
|
||||
bool
|
||||
heuristic_livelock_detection(const state * stuttering_succ,
|
||||
hash_type& h, int h_livelock_root, std::set<const state*,
|
||||
state_ptr_less_than> liveset_curr);
|
||||
|
||||
const_ta_product_ptr a_; ///< The automaton.
|
||||
option_map o_; ///< The options
|
||||
|
||||
// Force the second pass
|
||||
bool is_full_2_pass_;
|
||||
|
||||
// scc: a stack of strongly connected components (SCC)
|
||||
scc_stack_ta scc;
|
||||
|
||||
// sscc: a stack of strongly stuttering-connected components (SSCC)
|
||||
scc_stack_ta sscc;
|
||||
|
||||
};
|
||||
|
||||
/// @}
|
||||
|
||||
/// \addtogroup ta_emptiness_check_algorithms Emptiness-check algorithms
|
||||
/// \ingroup ta_emptiness_check
|
||||
}
|
||||
547
spot/taalgos/minimize.cc
Normal file
547
spot/taalgos/minimize.cc
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 Laboratoire de
|
||||
// Recherche et Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
|
||||
//#define TRACE
|
||||
|
||||
#ifdef TRACE
|
||||
# define trace std::cerr
|
||||
#else
|
||||
# define trace while (0) std::cerr
|
||||
#endif
|
||||
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <spot/taalgos/minimize.hh>
|
||||
#include <spot/misc/hash.hh>
|
||||
#include <spot/misc/bddlt.hh>
|
||||
#include <spot/ta/tgtaexplicit.hh>
|
||||
#include <spot/taalgos/statessetbuilder.hh>
|
||||
#include <spot/twa/twagraph.hh>
|
||||
#include <spot/twa/bddprint.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
typedef std::unordered_set<const state*,
|
||||
state_ptr_hash, state_ptr_equal> hash_set;
|
||||
typedef std::unordered_map<const state*, unsigned,
|
||||
state_ptr_hash, state_ptr_equal> hash_map;
|
||||
typedef std::list<hash_set*> partition_t;
|
||||
|
||||
namespace
|
||||
{
|
||||
static std::ostream&
|
||||
dump_hash_set(const hash_set* hs,
|
||||
const const_ta_ptr& aut,
|
||||
std::ostream& out)
|
||||
{
|
||||
out << '{';
|
||||
const char* sep = "";
|
||||
for (hash_set::const_iterator i = hs->begin(); i != hs->end(); ++i)
|
||||
{
|
||||
out << sep << aut->format_state(*i);
|
||||
sep = ", ";
|
||||
}
|
||||
out << '}';
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string
|
||||
format_hash_set(const hash_set* hs, const const_ta_ptr& aut)
|
||||
{
|
||||
std::ostringstream s;
|
||||
dump_hash_set(hs, aut, s);
|
||||
return s.str();
|
||||
}
|
||||
|
||||
// From the base automaton and the list of sets, build the minimal
|
||||
// automaton
|
||||
static void
|
||||
build_result(const const_ta_ptr& a, std::list<hash_set*>& sets,
|
||||
twa_graph_ptr result_tgba, const ta_explicit_ptr& result)
|
||||
{
|
||||
// For each set, create a state in the tgbaulting automaton.
|
||||
// For a state s, state_num[s] is the number of the state in the minimal
|
||||
// automaton.
|
||||
hash_map state_num;
|
||||
std::list<hash_set*>::iterator sit;
|
||||
unsigned num = 0;
|
||||
for (sit = sets.begin(); sit != sets.end(); ++sit)
|
||||
{
|
||||
hash_set::iterator hit;
|
||||
hash_set* h = *sit;
|
||||
for (hit = h->begin(); hit != h->end(); ++hit)
|
||||
state_num[*hit] = num;
|
||||
result_tgba->new_state();
|
||||
++num;
|
||||
}
|
||||
|
||||
// For each transition in the initial automaton, add the corresponding
|
||||
// transition in ta.
|
||||
|
||||
for (sit = sets.begin(); sit != sets.end(); ++sit)
|
||||
{
|
||||
hash_set::iterator hit;
|
||||
hash_set* h = *sit;
|
||||
hit = h->begin();
|
||||
const state* src = *hit;
|
||||
unsigned src_num = state_num[src];
|
||||
|
||||
bdd tgba_condition = bddtrue;
|
||||
bool is_initial_state = a->is_initial_state(src);
|
||||
if (!a->get_artificial_initial_state() && is_initial_state)
|
||||
tgba_condition = a->get_state_condition(src);
|
||||
bool is_accepting_state = a->is_accepting_state(src);
|
||||
bool is_livelock_accepting_state =
|
||||
a->is_livelock_accepting_state(src);
|
||||
|
||||
state_ta_explicit* new_src =
|
||||
new state_ta_explicit(result_tgba->state_from_number(src_num),
|
||||
tgba_condition, is_initial_state,
|
||||
is_accepting_state,
|
||||
is_livelock_accepting_state);
|
||||
|
||||
state_ta_explicit* ta_src = result->add_state(new_src);
|
||||
|
||||
if (ta_src != new_src)
|
||||
{
|
||||
delete new_src;
|
||||
}
|
||||
else if (a->get_artificial_initial_state())
|
||||
{
|
||||
if (a->get_artificial_initial_state() == src)
|
||||
result->set_artificial_initial_state(new_src);
|
||||
}
|
||||
else if (is_initial_state)
|
||||
{
|
||||
result->add_to_initial_states_set(new_src);
|
||||
}
|
||||
|
||||
ta_succ_iterator* succit = a->succ_iter(src);
|
||||
|
||||
for (succit->first(); !succit->done(); succit->next())
|
||||
{
|
||||
const state* dst = succit->dst();
|
||||
hash_map::const_iterator i = state_num.find(dst);
|
||||
|
||||
if (i == state_num.end()) // Ignore useless destinations.
|
||||
continue;
|
||||
|
||||
bdd tgba_condition = bddtrue;
|
||||
is_initial_state = a->is_initial_state(dst);
|
||||
if (!a->get_artificial_initial_state() && is_initial_state)
|
||||
tgba_condition = a->get_state_condition(dst);
|
||||
bool is_accepting_state = a->is_accepting_state(dst);
|
||||
bool is_livelock_accepting_state =
|
||||
a->is_livelock_accepting_state(dst);
|
||||
|
||||
state_ta_explicit* new_dst =
|
||||
new state_ta_explicit
|
||||
(result_tgba->state_from_number(i->second),
|
||||
tgba_condition, is_initial_state,
|
||||
is_accepting_state,
|
||||
is_livelock_accepting_state);
|
||||
|
||||
state_ta_explicit* ta_dst = result->add_state(new_dst);
|
||||
|
||||
if (ta_dst != new_dst)
|
||||
{
|
||||
delete new_dst;
|
||||
}
|
||||
else if (a->get_artificial_initial_state())
|
||||
{
|
||||
if (a->get_artificial_initial_state() == dst)
|
||||
result->set_artificial_initial_state(new_dst);
|
||||
}
|
||||
|
||||
else if (is_initial_state)
|
||||
result->add_to_initial_states_set(new_dst);
|
||||
|
||||
result->create_transition
|
||||
(ta_src, succit->cond(),
|
||||
succit->acc(),
|
||||
ta_dst);
|
||||
}
|
||||
delete succit;
|
||||
}
|
||||
}
|
||||
|
||||
static partition_t
|
||||
build_partition(const const_ta_ptr& ta_)
|
||||
{
|
||||
unsigned num_sets = ta_->acc().num_sets();
|
||||
std::map<acc_cond::mark_t, bdd> m2b;
|
||||
int acc_vars = ta_->get_dict()->register_anonymous_variables(num_sets,
|
||||
&m2b);
|
||||
auto mark_to_bdd = [&](acc_cond::mark_t m) -> bdd
|
||||
{
|
||||
auto i = m2b.find(m);
|
||||
if (i != m2b.end())
|
||||
return i->second;
|
||||
|
||||
bdd res = bddtrue;
|
||||
for (unsigned n = 0; n < num_sets; ++n)
|
||||
if (m.has(n))
|
||||
res &= bdd_ithvar(acc_vars + n);
|
||||
else
|
||||
res &= bdd_nithvar(acc_vars + n);
|
||||
m2b.emplace_hint(i, m, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
partition_t cur_run;
|
||||
partition_t next_run;
|
||||
|
||||
// The list of equivalent states.
|
||||
partition_t done;
|
||||
|
||||
std::set<const state*> states_set = get_states_set(ta_);
|
||||
|
||||
hash_set* I = new hash_set;
|
||||
|
||||
// livelock acceptance states
|
||||
hash_set* G = new hash_set;
|
||||
|
||||
// Buchi acceptance states
|
||||
hash_set* F = new hash_set;
|
||||
|
||||
// Buchi and livelock acceptance states
|
||||
hash_set* G_F = new hash_set;
|
||||
|
||||
// the other states (non initial and not in G, F and G_F)
|
||||
hash_set* S = new hash_set;
|
||||
|
||||
std::set<const state*>::iterator it;
|
||||
|
||||
auto artificial_initial_state = ta_->get_artificial_initial_state();
|
||||
|
||||
for (it = states_set.begin(); it != states_set.end(); ++it)
|
||||
{
|
||||
const state* s = *it;
|
||||
if (s == artificial_initial_state)
|
||||
I->insert(s);
|
||||
else if (!artificial_initial_state && ta_->is_initial_state(s))
|
||||
I->insert(s);
|
||||
else if (ta_->is_livelock_accepting_state(s)
|
||||
&& ta_->is_accepting_state(s))
|
||||
G_F->insert(s);
|
||||
else if (ta_->is_accepting_state(s))
|
||||
F->insert(s);
|
||||
else if (ta_->is_livelock_accepting_state(s))
|
||||
G->insert(s);
|
||||
else
|
||||
S->insert(s);
|
||||
}
|
||||
|
||||
hash_map state_set_map;
|
||||
|
||||
// Size of ta_
|
||||
unsigned size = states_set.size() + 6;
|
||||
// Use bdd variables to number sets. set_num is the first variable
|
||||
// available.
|
||||
unsigned set_num =
|
||||
ta_->get_dict()->register_anonymous_variables(size, &m2b);
|
||||
|
||||
std::set<int> free_var;
|
||||
for (unsigned i = set_num; i < set_num + size; ++i)
|
||||
free_var.insert(i);
|
||||
std::map<int, int> used_var;
|
||||
|
||||
for (hash_set::const_iterator i = I->begin(); i != I->end(); ++i)
|
||||
{
|
||||
hash_set* cI = new hash_set;
|
||||
cI->insert(*i);
|
||||
done.push_back(cI);
|
||||
|
||||
used_var[set_num] = 1;
|
||||
free_var.erase(set_num);
|
||||
state_set_map[*i] = set_num;
|
||||
++set_num;
|
||||
|
||||
}
|
||||
delete I;
|
||||
|
||||
if (!G->empty())
|
||||
{
|
||||
unsigned s = G->size();
|
||||
unsigned num = set_num;
|
||||
++set_num;
|
||||
used_var[num] = s;
|
||||
free_var.erase(num);
|
||||
if (s > 1)
|
||||
cur_run.push_back(G);
|
||||
else
|
||||
done.push_back(G);
|
||||
for (hash_set::const_iterator i = G->begin(); i != G->end(); ++i)
|
||||
state_set_map[*i] = num;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
delete G;
|
||||
}
|
||||
|
||||
if (!F->empty())
|
||||
{
|
||||
unsigned s = F->size();
|
||||
unsigned num = set_num;
|
||||
++set_num;
|
||||
used_var[num] = s;
|
||||
free_var.erase(num);
|
||||
if (s > 1)
|
||||
cur_run.push_back(F);
|
||||
else
|
||||
done.push_back(F);
|
||||
for (hash_set::const_iterator i = F->begin(); i != F->end(); ++i)
|
||||
state_set_map[*i] = num;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete F;
|
||||
}
|
||||
|
||||
if (!G_F->empty())
|
||||
{
|
||||
unsigned s = G_F->size();
|
||||
unsigned num = set_num;
|
||||
++set_num;
|
||||
used_var[num] = s;
|
||||
free_var.erase(num);
|
||||
if (s > 1)
|
||||
cur_run.push_back(G_F);
|
||||
else
|
||||
done.push_back(G_F);
|
||||
for (hash_set::const_iterator i = G_F->begin(); i != G_F->end(); ++i)
|
||||
state_set_map[*i] = num;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete G_F;
|
||||
}
|
||||
|
||||
if (!S->empty())
|
||||
{
|
||||
unsigned s = S->size();
|
||||
unsigned num = set_num;
|
||||
++set_num;
|
||||
used_var[num] = s;
|
||||
free_var.erase(num);
|
||||
if (s > 1)
|
||||
cur_run.push_back(S);
|
||||
else
|
||||
done.push_back(S);
|
||||
for (hash_set::const_iterator i = S->begin(); i != S->end(); ++i)
|
||||
state_set_map[*i] = num;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete S;
|
||||
}
|
||||
|
||||
|
||||
// A bdd_states_map is a list of formulae (in a BDD form)
|
||||
// associated with a destination set of states.
|
||||
typedef std::map<bdd, hash_set*, bdd_less_than> bdd_states_map;
|
||||
|
||||
bool did_split = true;
|
||||
unsigned num = set_num;
|
||||
++set_num;
|
||||
used_var[num] = 1;
|
||||
free_var.erase(num);
|
||||
bdd bdd_false_acceptance_condition = bdd_ithvar(num);
|
||||
|
||||
while (did_split)
|
||||
{
|
||||
did_split = false;
|
||||
while (!cur_run.empty())
|
||||
{
|
||||
// Get a set to process.
|
||||
hash_set* cur = cur_run.front();
|
||||
cur_run.pop_front();
|
||||
|
||||
trace
|
||||
<< "processing " << format_hash_set(cur, ta_) << std::endl;
|
||||
|
||||
hash_set::iterator hi;
|
||||
bdd_states_map bdd_map;
|
||||
for (hi = cur->begin(); hi != cur->end(); ++hi)
|
||||
{
|
||||
const state* src = *hi;
|
||||
bdd f = bddfalse;
|
||||
ta_succ_iterator* si = ta_->succ_iter(src);
|
||||
trace << "+src: " << src << std::endl;
|
||||
for (si->first(); !si->done(); si->next())
|
||||
{
|
||||
const state* dst = si->dst();
|
||||
hash_map::const_iterator i = state_set_map.find(dst);
|
||||
|
||||
assert(i != state_set_map.end());
|
||||
auto curacc =
|
||||
mark_to_bdd(si->acc());
|
||||
f |= (bdd_ithvar(i->second)
|
||||
& si->cond() & curacc);
|
||||
trace
|
||||
<< "+f: " << bdd_format_accset(ta_->get_dict(), f)
|
||||
<< "\n -bdd_ithvar(i->second): "
|
||||
<< bdd_format_accset(ta_->get_dict(),
|
||||
bdd_ithvar(i->second))
|
||||
<< "\n -si->cond(): "
|
||||
<< bdd_format_accset(ta_->get_dict(),
|
||||
si->cond())
|
||||
<< "\n -current_acceptance_conditions: "
|
||||
<< si->acc()
|
||||
<< std::endl;
|
||||
}
|
||||
delete si;
|
||||
|
||||
// Have we already seen this formula ?
|
||||
bdd_states_map::iterator bsi = bdd_map.find(f);
|
||||
if (bsi == bdd_map.end())
|
||||
{
|
||||
// No, create a new set.
|
||||
hash_set* new_set = new hash_set;
|
||||
new_set->insert(src);
|
||||
bdd_map[f] = new_set;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Yes, add the current state to the set.
|
||||
bsi->second->insert(src);
|
||||
}
|
||||
}
|
||||
|
||||
bdd_states_map::iterator bsi = bdd_map.begin();
|
||||
if (bdd_map.size() == 1)
|
||||
{
|
||||
// The set was not split.
|
||||
trace
|
||||
<< "set " << format_hash_set(bsi->second, ta_)
|
||||
<< " was not split" << std::endl;
|
||||
next_run.push_back(bsi->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
did_split = true;
|
||||
for (; bsi != bdd_map.end(); ++bsi)
|
||||
{
|
||||
hash_set* set = bsi->second;
|
||||
// Free the number associated to these states.
|
||||
unsigned num = state_set_map[*set->begin()];
|
||||
assert(used_var.find(num) != used_var.end());
|
||||
unsigned left = (used_var[num] -= set->size());
|
||||
// Make sure LEFT does not become negative
|
||||
// (hence bigger than SIZE when read as unsigned)
|
||||
assert(left < size);
|
||||
if (left == 0)
|
||||
{
|
||||
used_var.erase(num);
|
||||
free_var.insert(num);
|
||||
}
|
||||
// Pick a free number
|
||||
assert(!free_var.empty());
|
||||
num = *free_var.begin();
|
||||
free_var.erase(free_var.begin());
|
||||
used_var[num] = set->size();
|
||||
for (hash_set::iterator hit = set->begin();
|
||||
hit != set->end(); ++hit)
|
||||
state_set_map[*hit] = num;
|
||||
// Trivial sets can't be splitted any further.
|
||||
if (set->size() == 1)
|
||||
{
|
||||
trace
|
||||
<< "set " << format_hash_set(set, ta_)
|
||||
<< " is minimal" << std::endl;
|
||||
done.push_back(set);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace
|
||||
<< "set " << format_hash_set(set, ta_)
|
||||
<< " should be processed further" << std::endl;
|
||||
next_run.push_back(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete cur;
|
||||
}
|
||||
if (did_split)
|
||||
trace
|
||||
<< "splitting did occur during this pass." << std::endl;
|
||||
|
||||
std::swap(cur_run, next_run);
|
||||
}
|
||||
|
||||
done.splice(done.end(), cur_run);
|
||||
|
||||
#ifdef TRACE
|
||||
trace << "Final partition: ";
|
||||
for (partition_t::const_iterator i = done.begin(); i != done.end(); ++i)
|
||||
trace << format_hash_set(*i, ta_) << ' ';
|
||||
trace << std::endl;
|
||||
#endif
|
||||
|
||||
ta_->get_dict()->unregister_all_my_variables(&m2b);
|
||||
return done;
|
||||
}
|
||||
}
|
||||
|
||||
ta_explicit_ptr
|
||||
minimize_ta(const const_ta_ptr& ta_)
|
||||
{
|
||||
|
||||
auto tgba = make_twa_graph(ta_->get_dict());
|
||||
auto res = make_ta_explicit(tgba, ta_->acc().num_sets(), nullptr);
|
||||
|
||||
partition_t partition = build_partition(ta_);
|
||||
|
||||
// Build the ta automata result.
|
||||
build_result(ta_, partition, tgba, res);
|
||||
|
||||
// Free all the allocated memory.
|
||||
std::list<hash_set*>::iterator itdone;
|
||||
for (itdone = partition.begin(); itdone != partition.end(); ++itdone)
|
||||
delete *itdone;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
tgta_explicit_ptr
|
||||
minimize_tgta(const const_tgta_explicit_ptr& tgta_)
|
||||
{
|
||||
|
||||
auto tgba = make_twa_graph(tgta_->get_dict());
|
||||
auto res = make_tgta_explicit(tgba, tgta_->acc().num_sets(), nullptr);
|
||||
|
||||
auto ta = tgta_->get_ta();
|
||||
|
||||
partition_t partition = build_partition(ta);
|
||||
|
||||
// Build the minimal tgta automaton.
|
||||
build_result(ta, partition, tgba, res->get_ta());
|
||||
|
||||
// Free all the allocated memory.
|
||||
std::list<hash_set*>::iterator itdone;
|
||||
for (itdone = partition.begin(); itdone != partition.end(); ++itdone)
|
||||
delete *itdone;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
76
spot/taalgos/minimize.hh
Normal file
76
spot/taalgos/minimize.hh
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Laboratoire de
|
||||
// Recherche et Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/ta/ta.hh>
|
||||
#include <spot/ta/tgta.hh>
|
||||
#include <spot/ta/tgtaexplicit.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup ta_reduction
|
||||
/// @{
|
||||
|
||||
|
||||
/// \brief Construct a simplified TA by merging bisimilar states.
|
||||
///
|
||||
/// A TA automaton can be simplified by merging bisimilar states:
|
||||
/// Two states are bisimilar if the automaton can accept the
|
||||
/// same executions starting for either of these states. This can be
|
||||
/// achieved using any algorithm based on partition refinement
|
||||
///
|
||||
/// For more detail about this type of algorithm, see the following paper:
|
||||
/** \verbatim
|
||||
@InProceedings{valmari.09.icatpn,
|
||||
author = {Antti Valmari},
|
||||
title = {Bisimilarity Minimization in in O(m logn) Time},
|
||||
booktitle = {Proceedings of the 30th International Conference on
|
||||
the Applications and Theory of Petri Nets
|
||||
(ICATPN'09)},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
publisher = {Springer},
|
||||
isbn = {978-3-642-02423-8},
|
||||
pages = {123--142},
|
||||
volume = 5606,
|
||||
url = {http://dx.doi.org/10.1007/978-3-642-02424-5_9},
|
||||
year = {2009}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \param ta_ the TA automaton to convert into a simplified TA
|
||||
SPOT_API ta_explicit_ptr
|
||||
minimize_ta(const const_ta_ptr& ta_);
|
||||
|
||||
|
||||
|
||||
/// \brief Construct a simplified TGTA by merging bisimilar states.
|
||||
///
|
||||
/// A TGTA automaton can be simplified by merging bisimilar states:
|
||||
/// Two states are bisimilar if the automaton can accept the
|
||||
/// same executions starting for either of these states. This can be
|
||||
/// achieved using same algorithm used to simplify a TA taking into account
|
||||
/// the acceptance conditions of the outgoing transitions.
|
||||
///
|
||||
/// \param tgta_ the TGTA automaton to convert into a simplified TGTA
|
||||
SPOT_API tgta_explicit_ptr
|
||||
minimize_tgta(const const_tgta_explicit_ptr& tgta_);
|
||||
|
||||
/// @}
|
||||
}
|
||||
183
spot/taalgos/reachiter.cc
Normal file
183
spot/taalgos/reachiter.cc
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2012, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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 <cassert>
|
||||
#include <spot/taalgos/reachiter.hh>
|
||||
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
namespace spot
|
||||
{
|
||||
// ta_reachable_iterator
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
ta_reachable_iterator::ta_reachable_iterator(const const_ta_ptr& a) :
|
||||
t_automata_(a)
|
||||
{
|
||||
}
|
||||
ta_reachable_iterator::~ta_reachable_iterator()
|
||||
{
|
||||
seen_map::const_iterator s = seen.begin();
|
||||
while (s != seen.end())
|
||||
{
|
||||
// Advance the iterator before deleting the "key" pointer.
|
||||
const state* ptr = s->first;
|
||||
++s;
|
||||
t_automata_->free_state(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ta_reachable_iterator::run()
|
||||
{
|
||||
int n = 0;
|
||||
start();
|
||||
|
||||
const spot::state* artificial_initial_state =
|
||||
t_automata_->get_artificial_initial_state();
|
||||
|
||||
ta::const_states_set_t init_states_set;
|
||||
|
||||
if (artificial_initial_state)
|
||||
{
|
||||
init_states_set.insert(artificial_initial_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
init_states_set = t_automata_->get_initial_states_set();
|
||||
}
|
||||
|
||||
for (auto init_state: init_states_set)
|
||||
{
|
||||
if (want_state(init_state))
|
||||
add_state(init_state);
|
||||
seen[init_state] = ++n;
|
||||
}
|
||||
|
||||
const state* t;
|
||||
while ((t = next_state()))
|
||||
{
|
||||
assert(seen.find(t) != seen.end());
|
||||
int tn = seen[t];
|
||||
ta_succ_iterator* si = t_automata_->succ_iter(t);
|
||||
process_state(t, tn);
|
||||
for (si->first(); !si->done(); si->next())
|
||||
{
|
||||
const state* current = si->dst();
|
||||
seen_map::const_iterator s = seen.find(current);
|
||||
bool ws = want_state(current);
|
||||
if (s == seen.end())
|
||||
{
|
||||
seen[current] = ++n;
|
||||
if (ws)
|
||||
{
|
||||
add_state(current);
|
||||
process_link(tn, n, si);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ws)
|
||||
process_link(tn, s->second, si);
|
||||
t_automata_->free_state(current);
|
||||
}
|
||||
}
|
||||
delete si;
|
||||
}
|
||||
end();
|
||||
}
|
||||
|
||||
bool
|
||||
ta_reachable_iterator::want_state(const state*) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ta_reachable_iterator::start()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ta_reachable_iterator::end()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ta_reachable_iterator::process_state(const state*, int)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ta_reachable_iterator::process_link(int, int, const ta_succ_iterator*)
|
||||
{
|
||||
}
|
||||
|
||||
// ta_reachable_iterator_depth_first
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
ta_reachable_iterator_depth_first::ta_reachable_iterator_depth_first(
|
||||
const const_ta_ptr& a) :
|
||||
ta_reachable_iterator(a)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ta_reachable_iterator_depth_first::add_state(const state* s)
|
||||
{
|
||||
todo.push(s);
|
||||
}
|
||||
|
||||
const state*
|
||||
ta_reachable_iterator_depth_first::next_state()
|
||||
{
|
||||
if (todo.empty())
|
||||
return nullptr;
|
||||
const state* s = todo.top();
|
||||
todo.pop();
|
||||
return s;
|
||||
}
|
||||
|
||||
// ta_reachable_iterator_breadth_first
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
ta_reachable_iterator_breadth_first::ta_reachable_iterator_breadth_first(
|
||||
const const_ta_ptr& a) :
|
||||
ta_reachable_iterator(a)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ta_reachable_iterator_breadth_first::add_state(const state* s)
|
||||
{
|
||||
todo.push_back(s);
|
||||
}
|
||||
|
||||
const state*
|
||||
ta_reachable_iterator_breadth_first::next_state()
|
||||
{
|
||||
if (todo.empty())
|
||||
return nullptr;
|
||||
const state* s = todo.front();
|
||||
todo.pop_front();
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
131
spot/taalgos/reachiter.hh
Normal file
131
spot/taalgos/reachiter.hh
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2011, 2012, 2013, 2014 Laboratoire de Recherche
|
||||
// et Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/misc/hash.hh>
|
||||
#include <spot/ta/ta.hh>
|
||||
#include <stack>
|
||||
#include <deque>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup ta_generic
|
||||
/// \brief Iterate over all reachable states of a spot::ta.
|
||||
class SPOT_API ta_reachable_iterator
|
||||
{
|
||||
public:
|
||||
ta_reachable_iterator(const const_ta_ptr& a);
|
||||
virtual
|
||||
~ta_reachable_iterator();
|
||||
|
||||
/// \brief Iterate over all reachable states of a spot::ta.
|
||||
///
|
||||
/// This is a template method that will call add_state(), next_state(),
|
||||
/// start(), end(), process_state(), and process_link(), while it
|
||||
/// iterates over states.
|
||||
void
|
||||
run();
|
||||
|
||||
/// \name Todo list management.
|
||||
///
|
||||
/// spot::ta_reachable_iterator_depth_first and
|
||||
/// spot::ta_reachable_iterator_breadth_first offer two precanned
|
||||
/// implementations for these functions.
|
||||
/// \{
|
||||
/// \brief Called by run() to register newly discovered states.
|
||||
virtual void
|
||||
add_state(const state* s) = 0;
|
||||
/// \brief Called by run() to obtain the next state to process.
|
||||
virtual const state*
|
||||
next_state() = 0;
|
||||
/// \}
|
||||
|
||||
/// Called by add_state or next_states implementations to filter
|
||||
/// states. Default implementation always return true.
|
||||
virtual bool
|
||||
want_state(const state* s) const;
|
||||
|
||||
/// Called by run() before starting its iteration.
|
||||
virtual void
|
||||
start();
|
||||
/// Called by run() once all states have been explored.
|
||||
virtual void
|
||||
end();
|
||||
|
||||
/// Called by run() to process a state.
|
||||
///
|
||||
/// \param s The current state.
|
||||
/// \param n A unique number assigned to \a s.
|
||||
virtual void
|
||||
process_state(const state* s, int n);
|
||||
/// Called by run() to process a transition.
|
||||
///
|
||||
/// \param in The source state number.
|
||||
/// \param out The destination state number.
|
||||
/// \param si The spot::twa_succ_iterator positionned on the current
|
||||
/// transition.
|
||||
virtual void
|
||||
process_link(int in, int out, const ta_succ_iterator* si);
|
||||
|
||||
protected:
|
||||
|
||||
const_ta_ptr t_automata_; ///< The spot::ta to explore.
|
||||
|
||||
typedef std::unordered_map<const state*, int,
|
||||
state_ptr_hash, state_ptr_equal> seen_map;
|
||||
seen_map seen; ///< States already seen.
|
||||
};
|
||||
|
||||
/// \ingroup ta_generic
|
||||
/// \brief An implementation of spot::ta_reachable_iterator that browses
|
||||
/// states depth first.
|
||||
class SPOT_API ta_reachable_iterator_depth_first
|
||||
: public ta_reachable_iterator
|
||||
{
|
||||
public:
|
||||
ta_reachable_iterator_depth_first(const const_ta_ptr& a);
|
||||
|
||||
virtual void
|
||||
add_state(const state* s);
|
||||
virtual const state*
|
||||
next_state();
|
||||
|
||||
protected:
|
||||
std::stack<const state*> todo; ///< A stack of states yet to explore.
|
||||
};
|
||||
|
||||
/// \ingroup ta_generic
|
||||
/// \brief An implementation of spot::ta_reachable_iterator that browses
|
||||
/// states breadth first.
|
||||
class SPOT_API ta_reachable_iterator_breadth_first
|
||||
: public ta_reachable_iterator
|
||||
{
|
||||
public:
|
||||
ta_reachable_iterator_breadth_first(const const_ta_ptr& a);
|
||||
|
||||
virtual void
|
||||
add_state(const state* s);
|
||||
virtual const state*
|
||||
next_state();
|
||||
|
||||
protected:
|
||||
std::deque<const state*> todo; ///< A queue of states yet to explore.
|
||||
};
|
||||
}
|
||||
68
spot/taalgos/statessetbuilder.cc
Normal file
68
spot/taalgos/statessetbuilder.cc
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2014 Laboratoire de Recherche et Développement
|
||||
// de l'Epita (LRDE).
|
||||
//
|
||||
// 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 <iostream>
|
||||
#include <spot/ta/ta.hh>
|
||||
#include <spot/taalgos/statessetbuilder.hh>
|
||||
#include <spot/taalgos/reachiter.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class states_set_builder_bfs : public ta_reachable_iterator_breadth_first
|
||||
{
|
||||
public:
|
||||
states_set_builder_bfs(const const_ta_ptr& a) :
|
||||
ta_reachable_iterator_breadth_first(a)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
process_state(const state* s, int)
|
||||
{
|
||||
states_set_.insert(s);
|
||||
}
|
||||
|
||||
void
|
||||
process_link(int, int, const ta_succ_iterator*)
|
||||
{
|
||||
}
|
||||
|
||||
std::set<const state*>
|
||||
get_states_set()
|
||||
{
|
||||
return states_set_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<const state*> states_set_;
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
|
||||
|
||||
std::set<const state*>
|
||||
get_states_set(const const_ta_ptr& t)
|
||||
{
|
||||
states_set_builder_bfs d(t);
|
||||
d.run();
|
||||
return d.get_states_set();
|
||||
}
|
||||
}
|
||||
30
spot/taalgos/statessetbuilder.hh
Normal file
30
spot/taalgos/statessetbuilder.hh
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2013, 2014 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/ta/ta.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Compute states set for an automaton.
|
||||
SPOT_API std::set<const state*> get_states_set(const const_ta_ptr& t);
|
||||
|
||||
/// @}
|
||||
}
|
||||
78
spot/taalgos/stats.cc
Normal file
78
spot/taalgos/stats.cc
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004 Laboratoire d'Informatique de Paris 6 (LIP6),
|
||||
// département Systèmes Répartis Coopératifs (SRC), Université Pierre
|
||||
// et Marie Curie.
|
||||
//
|
||||
// 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 <iostream>
|
||||
#include <spot/ta/ta.hh>
|
||||
#include <spot/taalgos/stats.hh>
|
||||
#include <spot/taalgos/reachiter.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class stats_bfs : public ta_reachable_iterator_breadth_first
|
||||
{
|
||||
public:
|
||||
stats_bfs(const const_ta_ptr a, ta_statistics& s) :
|
||||
ta_reachable_iterator_breadth_first(a), s_(s)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
process_state(const state* s, int)
|
||||
{
|
||||
++s_.states;
|
||||
if (t_automata_->is_accepting_state(s)
|
||||
|| t_automata_->is_livelock_accepting_state(s))
|
||||
++s_.acceptance_states;
|
||||
}
|
||||
|
||||
void
|
||||
process_link(int, int, const ta_succ_iterator*)
|
||||
{
|
||||
++s_.edges;
|
||||
}
|
||||
|
||||
private:
|
||||
ta_statistics& s_;
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
|
||||
std::ostream&
|
||||
ta_statistics::dump(std::ostream& out) const
|
||||
{
|
||||
out << "edges: " << edges << std::endl;
|
||||
out << "states: " << states << std::endl;
|
||||
return out;
|
||||
}
|
||||
|
||||
ta_statistics
|
||||
stats_reachable(const const_ta_ptr& t)
|
||||
{
|
||||
ta_statistics s =
|
||||
{ 0, 0, 0};
|
||||
stats_bfs d(t, s);
|
||||
d.run();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
44
spot/taalgos/stats.hh
Normal file
44
spot/taalgos/stats.hh
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/ta/ta.hh>
|
||||
#include <iosfwd>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
/// \addtogroup ta_misc
|
||||
/// @{
|
||||
|
||||
struct SPOT_API ta_statistics
|
||||
{
|
||||
unsigned edges;
|
||||
unsigned states;
|
||||
unsigned acceptance_states;
|
||||
|
||||
std::ostream& dump(std::ostream& out) const;
|
||||
};
|
||||
|
||||
/// \brief Compute statistics for an automaton.
|
||||
SPOT_API ta_statistics stats_reachable(const const_ta_ptr& t);
|
||||
|
||||
/// @}
|
||||
}
|
||||
659
spot/taalgos/tgba2ta.cc
Normal file
659
spot/taalgos/tgba2ta.cc
Normal file
|
|
@ -0,0 +1,659 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 Laboratoire de
|
||||
// Recherche et Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
//#define TRACE
|
||||
|
||||
#include <iostream>
|
||||
#ifdef TRACE
|
||||
#define trace std::clog
|
||||
#else
|
||||
#define trace while (0) std::clog
|
||||
#endif
|
||||
|
||||
#include <spot/twa/formula2bdd.hh>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <spot/twa/bddprint.hh>
|
||||
#include <stack>
|
||||
#include <spot/taalgos/tgba2ta.hh>
|
||||
#include <spot/taalgos/statessetbuilder.hh>
|
||||
#include <spot/ta/tgtaexplicit.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef std::pair<const spot::state*, twa_succ_iterator*> pair_state_iter;
|
||||
|
||||
static void
|
||||
transform_to_single_pass_automaton
|
||||
(const ta_explicit_ptr& testing_automata,
|
||||
state_ta_explicit* artificial_livelock_acc_state = nullptr)
|
||||
{
|
||||
|
||||
if (artificial_livelock_acc_state)
|
||||
{
|
||||
auto artificial_livelock_acc_state_added =
|
||||
testing_automata->add_state(artificial_livelock_acc_state);
|
||||
|
||||
// unique artificial_livelock_acc_state
|
||||
assert(artificial_livelock_acc_state_added
|
||||
== artificial_livelock_acc_state);
|
||||
(void)artificial_livelock_acc_state_added;
|
||||
artificial_livelock_acc_state->set_livelock_accepting_state(true);
|
||||
artificial_livelock_acc_state->free_transitions();
|
||||
}
|
||||
|
||||
ta::states_set_t states_set = testing_automata->get_states_set();
|
||||
ta::states_set_t::iterator it;
|
||||
|
||||
state_ta_explicit::transitions* transitions_to_livelock_states =
|
||||
new state_ta_explicit::transitions;
|
||||
|
||||
for (it = states_set.begin(); it != states_set.end(); ++it)
|
||||
{
|
||||
auto source = const_cast<state_ta_explicit*>
|
||||
(static_cast<const state_ta_explicit*>(*it));
|
||||
|
||||
transitions_to_livelock_states->clear();
|
||||
|
||||
state_ta_explicit::transitions* trans = source->get_transitions();
|
||||
state_ta_explicit::transitions::iterator it_trans;
|
||||
|
||||
if (trans)
|
||||
for (it_trans = trans->begin(); it_trans != trans->end();)
|
||||
{
|
||||
auto dest = const_cast<state_ta_explicit*>((*it_trans)->dest);
|
||||
|
||||
state_ta_explicit::transitions* dest_trans =
|
||||
dest->get_transitions();
|
||||
bool dest_trans_empty = !dest_trans || dest_trans->empty();
|
||||
|
||||
//select transitions where a destination is a livelock state
|
||||
// which isn't a Buchi accepting state and has successors
|
||||
if (dest->is_livelock_accepting_state()
|
||||
&& (!dest->is_accepting_state()) && (!dest_trans_empty))
|
||||
transitions_to_livelock_states->push_front(*it_trans);
|
||||
|
||||
// optimization to have, after minimization, an unique
|
||||
// livelock state which has no successors
|
||||
if (dest->is_livelock_accepting_state() && (dest_trans_empty))
|
||||
dest->set_accepting_state(false);
|
||||
|
||||
++it_trans;
|
||||
}
|
||||
|
||||
if (transitions_to_livelock_states)
|
||||
{
|
||||
state_ta_explicit::transitions::iterator it_trans;
|
||||
|
||||
for (it_trans = transitions_to_livelock_states->begin();
|
||||
it_trans != transitions_to_livelock_states->end();
|
||||
++it_trans)
|
||||
{
|
||||
if (artificial_livelock_acc_state)
|
||||
{
|
||||
testing_automata->create_transition
|
||||
(source,
|
||||
(*it_trans)->condition,
|
||||
(*it_trans)->acceptance_conditions,
|
||||
artificial_livelock_acc_state, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
testing_automata->create_transition
|
||||
(source,
|
||||
(*it_trans)->condition,
|
||||
(*it_trans)->acceptance_conditions,
|
||||
((*it_trans)->dest)->stuttering_reachable_livelock,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delete transitions_to_livelock_states;
|
||||
|
||||
for (it = states_set.begin(); it != states_set.end(); ++it)
|
||||
{
|
||||
state_ta_explicit* state = static_cast<state_ta_explicit*> (*it);
|
||||
state_ta_explicit::transitions* state_trans =
|
||||
(state)->get_transitions();
|
||||
bool state_trans_empty = !state_trans || state_trans->empty();
|
||||
|
||||
if (state->is_livelock_accepting_state()
|
||||
&& (!state->is_accepting_state()) && (!state_trans_empty))
|
||||
state->set_livelock_accepting_state(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
compute_livelock_acceptance_states(const ta_explicit_ptr& testing_aut,
|
||||
bool single_pass_emptiness_check,
|
||||
state_ta_explicit*
|
||||
artificial_livelock_acc_state)
|
||||
{
|
||||
// We use five main data in this algorithm:
|
||||
// * sscc: a stack of strongly stuttering-connected components (SSCC)
|
||||
scc_stack_ta sscc;
|
||||
|
||||
// * arc, a stack of acceptance conditions between each of these SCC,
|
||||
std::stack<acc_cond::mark_t> arc;
|
||||
|
||||
// * h: a hash of all visited nodes, with their order,
|
||||
// (it is called "Hash" in Couvreur's paper)
|
||||
typedef std::unordered_map<const state*, int,
|
||||
state_ptr_hash, state_ptr_equal> hash_type;
|
||||
hash_type h; ///< Heap of visited states.
|
||||
|
||||
// * num: the number of visited nodes. Used to set the order of each
|
||||
// visited node,
|
||||
int num = 0;
|
||||
|
||||
// * todo: the depth-first search stack. This holds pairs of the
|
||||
// form (STATE, ITERATOR) where ITERATOR is a twa_succ_iterator
|
||||
// over the successors of STATE. In our use, ITERATOR should
|
||||
// always be freed when TODO is popped, but STATE should not because
|
||||
// it is also used as a key in H.
|
||||
std::stack<pair_state_iter> todo;
|
||||
|
||||
// * init: the set of the depth-first search initial states
|
||||
std::stack<const state*> init_set;
|
||||
|
||||
for (auto s: testing_aut->get_initial_states_set())
|
||||
init_set.push(s);
|
||||
|
||||
while (!init_set.empty())
|
||||
{
|
||||
// Setup depth-first search from initial states.
|
||||
|
||||
{
|
||||
auto init = down_cast<const state_ta_explicit*> (init_set.top());
|
||||
init_set.pop();
|
||||
|
||||
if (!h.emplace(init, num + 1).second)
|
||||
{
|
||||
init->destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
sscc.push(++num);
|
||||
arc.push(0U);
|
||||
sscc.top().is_accepting
|
||||
= testing_aut->is_accepting_state(init);
|
||||
twa_succ_iterator* iter = testing_aut->succ_iter(init);
|
||||
iter->first();
|
||||
todo.emplace(init, iter);
|
||||
}
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
auto curr = todo.top().first;
|
||||
|
||||
auto i = h.find(curr);
|
||||
// If we have reached a dead component, ignore it.
|
||||
if (i != h.end() && i->second == -1)
|
||||
{
|
||||
todo.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// We are looking at the next successor in SUCC.
|
||||
twa_succ_iterator* succ = todo.top().second;
|
||||
|
||||
// If there is no more successor, backtrack.
|
||||
if (succ->done())
|
||||
{
|
||||
// We have explored all successors of state CURR.
|
||||
|
||||
// Backtrack TODO.
|
||||
todo.pop();
|
||||
|
||||
// fill rem with any component removed,
|
||||
assert(i != h.end());
|
||||
sscc.rem().push_front(curr);
|
||||
|
||||
// When backtracking the root of an SSCC, we must also
|
||||
// remove that SSCC from the ROOT stacks. We must
|
||||
// discard from H all reachable states from this SSCC.
|
||||
assert(!sscc.empty());
|
||||
if (sscc.top().index == i->second)
|
||||
{
|
||||
// removing states
|
||||
bool is_livelock_accepting_sscc = (sscc.rem().size() > 1)
|
||||
&& ((sscc.top().is_accepting) ||
|
||||
(testing_aut->acc().
|
||||
accepting(sscc.top().condition)));
|
||||
trace << "*** sscc.size() = ***" << sscc.size() << '\n';
|
||||
for (auto j: sscc.rem())
|
||||
{
|
||||
h[j] = -1;
|
||||
|
||||
if (is_livelock_accepting_sscc)
|
||||
{
|
||||
// if it is an accepting sscc add the state to
|
||||
// G (=the livelock-accepting states set)
|
||||
trace << "*** sscc.size() > 1: states: ***"
|
||||
<< testing_aut->format_state(j)
|
||||
<< '\n';
|
||||
auto livelock_accepting_state =
|
||||
const_cast<state_ta_explicit*>
|
||||
(down_cast<const state_ta_explicit*>(j));
|
||||
|
||||
livelock_accepting_state->
|
||||
set_livelock_accepting_state(true);
|
||||
|
||||
if (single_pass_emptiness_check)
|
||||
{
|
||||
livelock_accepting_state
|
||||
->set_accepting_state(true);
|
||||
livelock_accepting_state
|
||||
->stuttering_reachable_livelock
|
||||
= livelock_accepting_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(!arc.empty());
|
||||
sscc.pop();
|
||||
arc.pop();
|
||||
}
|
||||
|
||||
// automata reduction
|
||||
testing_aut->delete_stuttering_and_hole_successors(curr);
|
||||
|
||||
delete succ;
|
||||
// Do not delete CURR: it is a key in H.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch the values destination state we are interested in...
|
||||
auto dest = succ->dst();
|
||||
|
||||
auto acc_cond = succ->acc();
|
||||
// ... and point the iterator to the next successor, for
|
||||
// the next iteration.
|
||||
succ->next();
|
||||
// We do not need SUCC from now on.
|
||||
|
||||
// Are we going to a new state through a stuttering transition?
|
||||
bool is_stuttering_transition =
|
||||
testing_aut->get_state_condition(curr)
|
||||
== testing_aut->get_state_condition(dest);
|
||||
auto id = h.find(dest);
|
||||
|
||||
// Is this a new state?
|
||||
if (id == h.end())
|
||||
{
|
||||
if (!is_stuttering_transition)
|
||||
{
|
||||
init_set.push(dest);
|
||||
dest->destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Number it, stack it, and register its successors
|
||||
// for later processing.
|
||||
h[dest] = ++num;
|
||||
sscc.push(num);
|
||||
arc.push(acc_cond);
|
||||
sscc.top().is_accepting =
|
||||
testing_aut->is_accepting_state(dest);
|
||||
|
||||
twa_succ_iterator* iter = testing_aut->succ_iter(dest);
|
||||
iter->first();
|
||||
todo.emplace(dest, iter);
|
||||
continue;
|
||||
}
|
||||
dest->destroy();
|
||||
|
||||
// If we have reached a dead component, ignore it.
|
||||
if (id->second == -1)
|
||||
continue;
|
||||
|
||||
trace << "***compute_livelock_acceptance_states: CYCLE***\n";
|
||||
|
||||
if (!curr->compare(id->first))
|
||||
{
|
||||
auto self_loop_state = const_cast<state_ta_explicit*>
|
||||
(down_cast<const state_ta_explicit*>(curr));
|
||||
assert(self_loop_state);
|
||||
|
||||
if (testing_aut->is_accepting_state(self_loop_state)
|
||||
|| (testing_aut->acc().accepting(acc_cond)))
|
||||
{
|
||||
self_loop_state->set_livelock_accepting_state(true);
|
||||
if (single_pass_emptiness_check)
|
||||
{
|
||||
self_loop_state->set_accepting_state(true);
|
||||
self_loop_state->stuttering_reachable_livelock
|
||||
= self_loop_state;
|
||||
}
|
||||
}
|
||||
|
||||
trace
|
||||
<< "***compute_livelock_acceptance_states: CYCLE: "
|
||||
<< "self_loop_state***\n";
|
||||
}
|
||||
|
||||
// Now this is the most interesting case. We have reached a
|
||||
// state S1 which is already part of a non-dead SSCC. Any such
|
||||
// non-dead SSCC has necessarily been crossed by our path to
|
||||
// this state: there is a state S2 in our path which belongs
|
||||
// to this SSCC too. We are going to merge all states between
|
||||
// this S1 and S2 into this SSCC.
|
||||
//
|
||||
// This merge is easy to do because the order of the SSCC in
|
||||
// ROOT is ascending: we just have to merge all SSCCs from the
|
||||
// top of ROOT that have an index greater to the one of
|
||||
// the SSCC of S2 (called the "threshold").
|
||||
int threshold = id->second;
|
||||
std::list<const state*> rem;
|
||||
bool acc = false;
|
||||
|
||||
while (threshold < sscc.top().index)
|
||||
{
|
||||
assert(!sscc.empty());
|
||||
assert(!arc.empty());
|
||||
acc |= sscc.top().is_accepting;
|
||||
acc_cond |= sscc.top().condition;
|
||||
acc_cond |= arc.top();
|
||||
rem.splice(rem.end(), sscc.rem());
|
||||
sscc.pop();
|
||||
arc.pop();
|
||||
}
|
||||
|
||||
// Note that we do not always have
|
||||
// threshold == sscc.top().index
|
||||
// after this loop, the SSCC whose index is threshold might have
|
||||
// been merged with a lower SSCC.
|
||||
|
||||
// Accumulate all acceptance conditions into the merged SSCC.
|
||||
sscc.top().is_accepting |= acc;
|
||||
sscc.top().condition |= acc_cond;
|
||||
|
||||
sscc.rem().splice(sscc.rem().end(), rem);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (artificial_livelock_acc_state || single_pass_emptiness_check)
|
||||
transform_to_single_pass_automaton(testing_aut,
|
||||
artificial_livelock_acc_state);
|
||||
}
|
||||
|
||||
ta_explicit_ptr
|
||||
build_ta(const ta_explicit_ptr& ta, bdd atomic_propositions_set_,
|
||||
bool degeneralized,
|
||||
bool single_pass_emptiness_check,
|
||||
bool artificial_livelock_state_mode,
|
||||
bool no_livelock)
|
||||
{
|
||||
|
||||
std::stack<state_ta_explicit*> todo;
|
||||
const_twa_ptr tgba_ = ta->get_tgba();
|
||||
|
||||
// build Initial states set:
|
||||
auto tgba_init_state = tgba_->get_init_state();
|
||||
|
||||
bdd tgba_condition = tgba_->support_conditions(tgba_init_state);
|
||||
|
||||
bool is_acc = false;
|
||||
if (degeneralized)
|
||||
{
|
||||
twa_succ_iterator* it = tgba_->succ_iter(tgba_init_state);
|
||||
it->first();
|
||||
if (!it->done())
|
||||
is_acc = it->acc() != 0U;
|
||||
delete it;
|
||||
}
|
||||
|
||||
bdd satone_tgba_condition;
|
||||
while ((satone_tgba_condition = bdd_satoneset(tgba_condition,
|
||||
atomic_propositions_set_,
|
||||
bddtrue)) != bddfalse)
|
||||
{
|
||||
tgba_condition -= satone_tgba_condition;
|
||||
state_ta_explicit* init_state = new
|
||||
state_ta_explicit(tgba_init_state->clone(),
|
||||
satone_tgba_condition, true, is_acc);
|
||||
state_ta_explicit* s = ta->add_state(init_state);
|
||||
assert(s == init_state);
|
||||
ta->add_to_initial_states_set(s);
|
||||
|
||||
todo.push(init_state);
|
||||
}
|
||||
tgba_init_state->destroy();
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
state_ta_explicit* source = todo.top();
|
||||
todo.pop();
|
||||
|
||||
twa_succ_iterator* twa_succ_it =
|
||||
tgba_->succ_iter(source->get_tgba_state());
|
||||
for (twa_succ_it->first(); !twa_succ_it->done();
|
||||
twa_succ_it->next())
|
||||
{
|
||||
const state* tgba_state = twa_succ_it->dst();
|
||||
bdd tgba_condition = twa_succ_it->cond();
|
||||
acc_cond::mark_t tgba_acceptance_conditions =
|
||||
twa_succ_it->acc();
|
||||
bdd satone_tgba_condition;
|
||||
while ((satone_tgba_condition =
|
||||
bdd_satoneset(tgba_condition,
|
||||
atomic_propositions_set_, bddtrue))
|
||||
!= bddfalse)
|
||||
{
|
||||
tgba_condition -= satone_tgba_condition;
|
||||
|
||||
bdd all_props = bddtrue;
|
||||
bdd dest_condition;
|
||||
|
||||
bool is_acc = false;
|
||||
if (degeneralized)
|
||||
{
|
||||
twa_succ_iterator* it = tgba_->succ_iter(tgba_state);
|
||||
it->first();
|
||||
if (!it->done())
|
||||
is_acc = it->acc() != 0U;
|
||||
delete it;
|
||||
}
|
||||
|
||||
if (satone_tgba_condition == source->get_tgba_condition())
|
||||
while ((dest_condition =
|
||||
bdd_satoneset(all_props,
|
||||
atomic_propositions_set_, bddtrue))
|
||||
!= bddfalse)
|
||||
{
|
||||
all_props -= dest_condition;
|
||||
state_ta_explicit* new_dest =
|
||||
new state_ta_explicit(tgba_state->clone(),
|
||||
dest_condition, false, is_acc);
|
||||
state_ta_explicit* dest = ta->add_state(new_dest);
|
||||
|
||||
if (dest != new_dest)
|
||||
{
|
||||
// the state dest already exists in the automaton
|
||||
new_dest->get_tgba_state()->destroy();
|
||||
delete new_dest;
|
||||
}
|
||||
else
|
||||
{
|
||||
todo.push(dest);
|
||||
}
|
||||
|
||||
bdd cs = bdd_setxor(source->get_tgba_condition(),
|
||||
dest->get_tgba_condition());
|
||||
ta->create_transition(source, cs,
|
||||
tgba_acceptance_conditions, dest);
|
||||
}
|
||||
}
|
||||
tgba_state->destroy();
|
||||
}
|
||||
delete twa_succ_it;
|
||||
}
|
||||
|
||||
if (no_livelock)
|
||||
return ta;
|
||||
|
||||
state_ta_explicit* artificial_livelock_acc_state = nullptr;
|
||||
|
||||
trace << "*** build_ta: artificial_livelock_acc_state_mode = ***"
|
||||
<< artificial_livelock_state_mode << std::endl;
|
||||
|
||||
if (artificial_livelock_state_mode)
|
||||
{
|
||||
single_pass_emptiness_check = true;
|
||||
artificial_livelock_acc_state =
|
||||
new state_ta_explicit(ta->get_tgba()->get_init_state(), bddtrue,
|
||||
false, false, true, nullptr);
|
||||
trace
|
||||
<< "*** build_ta: artificial_livelock_acc_state = ***"
|
||||
<< artificial_livelock_acc_state << std::endl;
|
||||
}
|
||||
|
||||
compute_livelock_acceptance_states(ta, single_pass_emptiness_check,
|
||||
artificial_livelock_acc_state);
|
||||
return ta;
|
||||
}
|
||||
}
|
||||
|
||||
ta_explicit_ptr
|
||||
tgba_to_ta(const const_twa_ptr& tgba_, bdd atomic_propositions_set_,
|
||||
bool degeneralized, bool artificial_initial_state_mode,
|
||||
bool single_pass_emptiness_check,
|
||||
bool artificial_livelock_state_mode,
|
||||
bool no_livelock)
|
||||
{
|
||||
ta_explicit_ptr ta;
|
||||
|
||||
auto tgba_init_state = tgba_->get_init_state();
|
||||
if (artificial_initial_state_mode)
|
||||
{
|
||||
state_ta_explicit* artificial_init_state =
|
||||
new state_ta_explicit(tgba_init_state->clone(), bddfalse, true);
|
||||
|
||||
ta = make_ta_explicit(tgba_, tgba_->acc().num_sets(),
|
||||
artificial_init_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
ta = make_ta_explicit(tgba_, tgba_->acc().num_sets());
|
||||
}
|
||||
tgba_init_state->destroy();
|
||||
|
||||
// build ta automaton
|
||||
build_ta(ta, atomic_propositions_set_, degeneralized,
|
||||
single_pass_emptiness_check, artificial_livelock_state_mode,
|
||||
no_livelock);
|
||||
|
||||
// (degeneralized=true) => TA
|
||||
if (degeneralized)
|
||||
return ta;
|
||||
|
||||
// (degeneralized=false) => GTA
|
||||
// adapt a GTA to remove acceptance conditions from states
|
||||
ta::states_set_t states_set = ta->get_states_set();
|
||||
ta::states_set_t::iterator it;
|
||||
for (it = states_set.begin(); it != states_set.end(); ++it)
|
||||
{
|
||||
state_ta_explicit* state = static_cast<state_ta_explicit*> (*it);
|
||||
|
||||
if (state->is_accepting_state())
|
||||
{
|
||||
state_ta_explicit::transitions* trans = state->get_transitions();
|
||||
state_ta_explicit::transitions::iterator it_trans;
|
||||
|
||||
for (it_trans = trans->begin(); it_trans != trans->end();
|
||||
++it_trans)
|
||||
(*it_trans)->acceptance_conditions = ta->acc().all_sets();
|
||||
|
||||
state->set_accepting_state(false);
|
||||
}
|
||||
}
|
||||
|
||||
return ta;
|
||||
}
|
||||
|
||||
tgta_explicit_ptr
|
||||
tgba_to_tgta(const const_twa_ptr& tgba_, bdd atomic_propositions_set_)
|
||||
{
|
||||
auto tgba_init_state = tgba_->get_init_state();
|
||||
auto artificial_init_state = new state_ta_explicit(tgba_init_state->clone(),
|
||||
bddfalse, true);
|
||||
tgba_init_state->destroy();
|
||||
|
||||
auto tgta = make_tgta_explicit(tgba_, tgba_->acc().num_sets(),
|
||||
artificial_init_state);
|
||||
|
||||
// build a Generalized TA automaton involving a single_pass_emptiness_check
|
||||
// (without an artificial livelock state):
|
||||
auto ta = tgta->get_ta();
|
||||
build_ta(ta, atomic_propositions_set_, false, true, false, false);
|
||||
|
||||
trace << "***tgba_to_tgbta: POST build_ta***" << std::endl;
|
||||
|
||||
// adapt a ta automata to build tgta automata :
|
||||
ta::states_set_t states_set = ta->get_states_set();
|
||||
ta::states_set_t::iterator it;
|
||||
twa_succ_iterator* initial_states_iter =
|
||||
ta->succ_iter(ta->get_artificial_initial_state());
|
||||
initial_states_iter->first();
|
||||
if (initial_states_iter->done())
|
||||
{
|
||||
delete initial_states_iter;
|
||||
return tgta;
|
||||
}
|
||||
bdd first_state_condition = initial_states_iter->cond();
|
||||
delete initial_states_iter;
|
||||
|
||||
bdd bdd_stutering_transition = bdd_setxor(first_state_condition,
|
||||
first_state_condition);
|
||||
|
||||
for (it = states_set.begin(); it != states_set.end(); ++it)
|
||||
{
|
||||
state_ta_explicit* state = static_cast<state_ta_explicit*> (*it);
|
||||
|
||||
state_ta_explicit::transitions* trans = state->get_transitions();
|
||||
if (state->is_livelock_accepting_state())
|
||||
{
|
||||
bool trans_empty = !trans || trans->empty();
|
||||
if (trans_empty || state->is_accepting_state())
|
||||
{
|
||||
ta->create_transition(state, bdd_stutering_transition,
|
||||
ta->acc().all_sets(), state);
|
||||
}
|
||||
}
|
||||
|
||||
if (state->compare(ta->get_artificial_initial_state()))
|
||||
ta->create_transition(state, bdd_stutering_transition,
|
||||
0U, state);
|
||||
|
||||
state->set_livelock_accepting_state(false);
|
||||
state->set_accepting_state(false);
|
||||
trace << "***tgba_to_tgbta: POST create_transition ***" << std::endl;
|
||||
}
|
||||
|
||||
return tgta;
|
||||
}
|
||||
}
|
||||
104
spot/taalgos/tgba2ta.hh
Normal file
104
spot/taalgos/tgba2ta.hh
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2012, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
//
|
||||
// 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/twa.hh>
|
||||
#include <spot/ta/taexplicit.hh>
|
||||
#include <spot/ta/tgtaexplicit.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup tgba_ta
|
||||
/// \brief Build a spot::ta_explicit* (TA) from an LTL formula.
|
||||
///
|
||||
/// This is based on the following paper.
|
||||
/** \verbatim
|
||||
@InProceedings{ geldenhuys.06.spin,
|
||||
author = {Jaco Geldenhuys and Henri Hansen},
|
||||
title = {Larger Automata and Less Work for {LTL} Model Checking},
|
||||
booktitle = {Proceedings of the 13th International SPIN Workshop
|
||||
(SPIN'06)},
|
||||
year = {2006},
|
||||
pages = {53--70},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
volume = {3925},
|
||||
publisher = {Springer}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \param tgba_to_convert The TGBA automaton to convert into a TA automaton
|
||||
///
|
||||
/// \param atomic_propositions_set The set of atomic propositions used in the
|
||||
/// input TGBA \a tgba_to_convert
|
||||
///
|
||||
/// \param degeneralized When false, the returned automaton is a generalized
|
||||
/// form of TA, called GTA (Generalized Testing Automaton).
|
||||
/// Like TGBA, GTA use Generalized Büchi acceptance
|
||||
/// conditions intead of Buchi-accepting states: there are several acceptance
|
||||
/// sets (of transitions), and a path is accepted if it traverses
|
||||
/// at least one transition of each set infinitely often or if it contains a
|
||||
/// livelock-accepting cycle (like a TA). The spot emptiness check algorithm
|
||||
/// for TA (spot::ta_check::check) can also be used to check GTA.
|
||||
///
|
||||
/// \param artificial_initial_state_mode When set, the algorithm will build
|
||||
/// a TA automaton with an unique initial state. This
|
||||
/// artificial initial state have one transition to each real initial state,
|
||||
/// and this transition is labeled by the corresponding initial condition.
|
||||
/// (see spot::ta::get_artificial_initial_state())
|
||||
///
|
||||
/// \param single_pass_emptiness_check When set, the product between the
|
||||
/// returned automaton and a kripke structure requires only the fist pass of
|
||||
/// the emptiness check algorithm (see the parameter \c disable_second_pass
|
||||
/// of the method spot::ta_check::check)
|
||||
///
|
||||
///
|
||||
/// \param artificial_livelock_state_mode When set, the returned TA automaton
|
||||
/// is a STA (Single-pass Testing Automata): a STA automaton is a TA
|
||||
/// where: for every livelock-accepting state s, if s is not also a
|
||||
/// Buchi-accepting state, then s has no successors. A STA product requires
|
||||
/// only one-pass emptiness check algorithm (see spot::ta_check::check)
|
||||
///
|
||||
/// \param no_livelock when set, this disable the replacement of
|
||||
/// stuttering components by livelock states. Use this flag to
|
||||
/// demonstrate an intermediate step of the construction.
|
||||
///
|
||||
/// \return A spot::ta_explicit that recognizes the same language as the
|
||||
/// TGBA \a tgba_to_convert.
|
||||
SPOT_API ta_explicit_ptr
|
||||
tgba_to_ta(const const_twa_ptr& tgba_to_convert, bdd atomic_propositions_set,
|
||||
bool degeneralized = true,
|
||||
bool artificial_initial_state_mode = true,
|
||||
bool single_pass_emptiness_check = false,
|
||||
bool artificial_livelock_state_mode = false,
|
||||
bool no_livelock = false);
|
||||
|
||||
/// \ingroup tgba_ta
|
||||
/// \brief Build a spot::tgta_explicit* (TGTA) from an LTL formula.
|
||||
///
|
||||
/// \param tgba_to_convert The TGBA automaton to convert into a TGTA automaton
|
||||
/// \param atomic_propositions_set The set of atomic propositions used in the
|
||||
/// input TGBA \a tgba_to_convert
|
||||
///
|
||||
/// \return A spot::tgta_explicit (spot::tgta) that recognizes the same
|
||||
/// language as the TGBA \a tgba_to_convert.
|
||||
SPOT_API tgta_explicit_ptr
|
||||
tgba_to_tgta(const const_twa_ptr& tgba_to_convert,
|
||||
bdd atomic_propositions_set);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue