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
6
src/twaalgos/.gitignore
vendored
6
src/twaalgos/.gitignore
vendored
|
|
@ -1,6 +0,0 @@
|
|||
.deps
|
||||
.libs
|
||||
*.lo
|
||||
*.la
|
||||
Makefile
|
||||
Makefile.in
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
## Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Laboratoire
|
||||
## de Recherche et Développement de l'Epita (LRDE).
|
||||
## Copyright (C) 2003, 2004, 2005 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/>.
|
||||
|
||||
SUBDIRS = gtec
|
||||
|
||||
AM_CPPFLAGS = -I$(srcdir)/.. -I.. $(BUDDY_CPPFLAGS)
|
||||
AM_CXXFLAGS = $(WARNING_CXXFLAGS)
|
||||
|
||||
twaalgosdir = $(pkgincludedir)/twaalgos
|
||||
|
||||
twaalgos_HEADERS = \
|
||||
are_isomorphic.hh \
|
||||
bfssteps.hh \
|
||||
canonicalize.hh \
|
||||
cleanacc.hh \
|
||||
complete.hh \
|
||||
complement.hh \
|
||||
compsusp.hh \
|
||||
copy.hh \
|
||||
cycles.hh \
|
||||
degen.hh \
|
||||
dot.hh \
|
||||
dtbasat.hh \
|
||||
dtwasat.hh \
|
||||
emptiness.hh \
|
||||
emptiness_stats.hh \
|
||||
gv04.hh \
|
||||
hoa.hh \
|
||||
isdet.hh \
|
||||
isunamb.hh \
|
||||
isweakscc.hh \
|
||||
lbtt.hh \
|
||||
ltl2taa.hh \
|
||||
ltl2tgba_fm.hh \
|
||||
magic.hh \
|
||||
mask.hh \
|
||||
minimize.hh \
|
||||
neverclaim.hh \
|
||||
postproc.hh \
|
||||
powerset.hh \
|
||||
product.hh \
|
||||
projrun.hh \
|
||||
randomgraph.hh \
|
||||
randomize.hh \
|
||||
reachiter.hh \
|
||||
relabel.hh \
|
||||
remfin.hh \
|
||||
remprop.hh \
|
||||
strength.hh \
|
||||
sbacc.hh \
|
||||
sccfilter.hh \
|
||||
sccinfo.hh \
|
||||
se05.hh \
|
||||
sepsets.hh \
|
||||
simulation.hh \
|
||||
stats.hh \
|
||||
stripacc.hh \
|
||||
stutter.hh \
|
||||
tau03.hh \
|
||||
tau03opt.hh \
|
||||
totgba.hh \
|
||||
translate.hh \
|
||||
word.hh
|
||||
|
||||
noinst_LTLIBRARIES = libtwaalgos.la
|
||||
libtwaalgos_la_SOURCES = \
|
||||
are_isomorphic.cc \
|
||||
bfssteps.cc \
|
||||
canonicalize.cc \
|
||||
cleanacc.cc \
|
||||
complete.cc \
|
||||
complement.cc \
|
||||
compsusp.cc \
|
||||
copy.cc \
|
||||
cycles.cc \
|
||||
degen.cc \
|
||||
dot.cc \
|
||||
dtbasat.cc \
|
||||
dtwasat.cc \
|
||||
emptiness.cc \
|
||||
gv04.cc \
|
||||
hoa.cc \
|
||||
isdet.cc \
|
||||
isunamb.cc \
|
||||
isweakscc.cc \
|
||||
lbtt.cc \
|
||||
ltl2taa.cc \
|
||||
ltl2tgba_fm.cc \
|
||||
magic.cc \
|
||||
mask.cc \
|
||||
minimize.cc \
|
||||
ndfs_result.hxx \
|
||||
neverclaim.cc \
|
||||
postproc.cc \
|
||||
powerset.cc \
|
||||
product.cc \
|
||||
projrun.cc \
|
||||
randomgraph.cc \
|
||||
randomize.cc \
|
||||
reachiter.cc \
|
||||
remfin.cc \
|
||||
remprop.cc \
|
||||
relabel.cc \
|
||||
strength.cc \
|
||||
sbacc.cc \
|
||||
sccinfo.cc \
|
||||
sccfilter.cc \
|
||||
se05.cc \
|
||||
sepsets.cc \
|
||||
simulation.cc \
|
||||
stats.cc \
|
||||
stripacc.cc \
|
||||
stutter.cc \
|
||||
tau03.cc \
|
||||
tau03opt.cc \
|
||||
totgba.cc \
|
||||
translate.cc \
|
||||
weight.cc \
|
||||
weight.hh \
|
||||
word.cc
|
||||
|
||||
libtwaalgos_la_LIBADD = gtec/libgtec.la
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 "twa/twagraph.hh"
|
||||
#include "twaalgos/are_isomorphic.hh"
|
||||
#include "twaalgos/canonicalize.hh"
|
||||
#include "twaalgos/isdet.hh"
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef spot::twa_graph::graph_t::edge_storage_t tr_t;
|
||||
bool
|
||||
tr_t_less_than(const tr_t& t1, const tr_t& t2)
|
||||
{
|
||||
return t1.cond.id() < t2.cond.id();
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(const tr_t& t1, const tr_t& t2)
|
||||
{
|
||||
return t1.cond.id() != t2.cond.id();
|
||||
}
|
||||
|
||||
bool
|
||||
are_isomorphic_det(const spot::const_twa_graph_ptr aut1,
|
||||
const spot::const_twa_graph_ptr aut2)
|
||||
{
|
||||
typedef std::pair<unsigned, unsigned> state_pair_t;
|
||||
std::queue<state_pair_t> workqueue;
|
||||
workqueue.emplace(aut1->get_init_state_number(),
|
||||
aut2->get_init_state_number());
|
||||
std::vector<unsigned> map(aut1->num_states(), -1U);
|
||||
map[aut1->get_init_state_number()] = aut2->get_init_state_number();
|
||||
std::vector<tr_t> trans1;
|
||||
std::vector<tr_t> trans2;
|
||||
state_pair_t current_state;
|
||||
while (!workqueue.empty())
|
||||
{
|
||||
current_state = workqueue.front();
|
||||
workqueue.pop();
|
||||
|
||||
for (auto& t : aut1->out(current_state.first))
|
||||
trans1.emplace_back(t);
|
||||
for (auto& t : aut2->out(current_state.second))
|
||||
trans2.emplace_back(t);
|
||||
|
||||
if (trans1.size() != trans2.size())
|
||||
return false;
|
||||
|
||||
std::sort(trans1.begin(), trans1.end(), tr_t_less_than);
|
||||
std::sort(trans2.begin(), trans2.end(), tr_t_less_than);
|
||||
|
||||
for (auto t1 = trans1.begin(), t2 = trans2.begin();
|
||||
t1 != trans1.end() && t2 != trans2.end();
|
||||
++t1, ++t2)
|
||||
{
|
||||
if (*t1 != *t2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (map[t1->dst] == -1U)
|
||||
{
|
||||
map[t1->dst] = t2->dst;
|
||||
workqueue.emplace(t1->dst, t2->dst);
|
||||
}
|
||||
else if (map[t1->dst] != t2->dst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
trans1.clear();
|
||||
trans2.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
trivially_different(const spot::const_twa_graph_ptr aut1,
|
||||
const spot::const_twa_graph_ptr aut2)
|
||||
{
|
||||
return aut1->num_states() != aut2->num_states() ||
|
||||
aut1->num_edges() != aut2->num_edges() ||
|
||||
// FIXME: At some point, it would be nice to support reordering
|
||||
// of acceptance sets (issue #58).
|
||||
aut1->acc().get_acceptance() != aut2->acc().get_acceptance();
|
||||
}
|
||||
}
|
||||
|
||||
namespace spot
|
||||
{
|
||||
isomorphism_checker::isomorphism_checker(const const_twa_graph_ptr ref)
|
||||
{
|
||||
ref_ = make_twa_graph(ref, twa::prop_set::all());
|
||||
ref_deterministic_ = ref_->prop_deterministic();
|
||||
if (!ref_deterministic_)
|
||||
{
|
||||
nondet_states_ = spot::count_nondet_states(ref_);
|
||||
ref_deterministic_ = (nondet_states_ == 0);
|
||||
}
|
||||
canonicalize(ref_);
|
||||
}
|
||||
|
||||
bool
|
||||
isomorphism_checker::is_isomorphic(const const_twa_graph_ptr aut)
|
||||
{
|
||||
if (trivially_different(ref_, aut))
|
||||
return false;
|
||||
|
||||
if (ref_deterministic_)
|
||||
{
|
||||
if (aut->prop_deterministic() || spot::is_deterministic(aut))
|
||||
{
|
||||
return are_isomorphic_det(ref_, aut);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aut->prop_deterministic() ||
|
||||
nondet_states_ != spot::count_nondet_states(aut))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto tmp = make_twa_graph(aut, twa::prop_set::all());
|
||||
spot::canonicalize(tmp);
|
||||
return *tmp == *ref_;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "twa/twagraph.hh"
|
||||
#include <vector>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// Check if two automata are isomorphic.
|
||||
class SPOT_API isomorphism_checker
|
||||
{
|
||||
public:
|
||||
isomorphism_checker(const const_twa_graph_ptr ref);
|
||||
|
||||
/// \ingroup twa_misc
|
||||
/// \brief Check whether an automaton is isomorphic to the one passed to
|
||||
/// the constructor.
|
||||
///
|
||||
/// Two automata are considered isomorphic if there exists a bijection f
|
||||
/// between the states of a1 and the states of a2 such that for any pair of
|
||||
/// states (s1, s2) of a1, there is a transition from s1 to s2 with
|
||||
/// condition c and acceptance set A iff there is a transition with
|
||||
/// condition c and acceptance set A between f(s1) and f(s2) in a2.
|
||||
/// This can be done simply by checking if
|
||||
/// canonicalize(aut1) == canonicalize(aut2), but is_isomorphic can do some
|
||||
/// optimizations in some cases.
|
||||
bool
|
||||
is_isomorphic(const const_twa_graph_ptr aut);
|
||||
|
||||
private:
|
||||
twa_graph_ptr ref_;
|
||||
bool ref_deterministic_ = false;
|
||||
unsigned nondet_states_ = 0;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 <deque>
|
||||
#include <utility>
|
||||
#include "twa/twa.hh"
|
||||
#include "bfssteps.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
bfs_steps::bfs_steps(const const_twa_ptr& a)
|
||||
: a_(a)
|
||||
{
|
||||
}
|
||||
|
||||
bfs_steps::~bfs_steps()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
bfs_steps::finalize(const std::map<const state*, twa_run::step,
|
||||
state_ptr_less_than>& father, const twa_run::step& s,
|
||||
const state* start, twa_run::steps& l)
|
||||
{
|
||||
twa_run::steps p;
|
||||
twa_run::step current = s;
|
||||
for (;;)
|
||||
{
|
||||
twa_run::step tmp = current;
|
||||
tmp.s = tmp.s->clone();
|
||||
p.push_front(tmp);
|
||||
if (current.s == start)
|
||||
break;
|
||||
std::map<const state*, twa_run::step,
|
||||
state_ptr_less_than>::const_iterator it = father.find(current.s);
|
||||
assert(it != father.end());
|
||||
current = it->second;
|
||||
}
|
||||
l.splice(l.end(), p);
|
||||
}
|
||||
|
||||
const state*
|
||||
bfs_steps::search(const state* start, twa_run::steps& l)
|
||||
{
|
||||
// Records backlinks to parent state during the BFS.
|
||||
// (This also stores the propositions of this link.)
|
||||
std::map<const state*, twa_run::step,
|
||||
state_ptr_less_than> father;
|
||||
// BFS queue.
|
||||
std::deque<const state*> todo;
|
||||
// Initial state.
|
||||
todo.push_back(start);
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
const state* src = todo.front();
|
||||
todo.pop_front();
|
||||
for (auto i: a_->succ(src))
|
||||
{
|
||||
const state* dest = filter(i->dst());
|
||||
|
||||
if (!dest)
|
||||
continue;
|
||||
|
||||
bdd cond = i->cond();
|
||||
acc_cond::mark_t acc = i->acc();
|
||||
twa_run::step s = { src, cond, acc };
|
||||
|
||||
if (match(s, dest))
|
||||
{
|
||||
// Found it!
|
||||
finalize(father, s, start, l);
|
||||
return dest;
|
||||
}
|
||||
|
||||
// Common case: record backlinks and continue BFS
|
||||
// for unvisited states.
|
||||
if (father.find(dest) == father.end())
|
||||
{
|
||||
todo.push_back(dest);
|
||||
father[dest] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014 Laboratoire de Recherche et Developpement de
|
||||
// l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 <map>
|
||||
#include "emptiness.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_misc
|
||||
/// \brief Make a BFS in a spot::tgba to compute a twa_run::steps.
|
||||
///
|
||||
/// This class should be used to compute the shortest path
|
||||
/// between a state of a spot::tgba and the first transition or
|
||||
/// state that matches some conditions.
|
||||
///
|
||||
/// These conditions should be specified by defining bfs_steps::match()
|
||||
/// in a subclass. Also the search can be restricted to some set of
|
||||
/// states with a proper definition of bfs_steps::filter().
|
||||
class SPOT_API bfs_steps
|
||||
{
|
||||
public:
|
||||
bfs_steps(const const_twa_ptr& a);
|
||||
virtual ~bfs_steps();
|
||||
|
||||
/// \brief Start the search from \a start, and append the
|
||||
/// resulting path (if any) to \a l.
|
||||
///
|
||||
/// \return the destination state of the last step (not included
|
||||
/// in \a l) if a matching path was found, or 0 otherwise.
|
||||
const state* search(const state* start, twa_run::steps& l);
|
||||
|
||||
/// \brief Return a state* that is unique for \a a.
|
||||
///
|
||||
/// bfs_steps does not do handle the memory for the states it
|
||||
/// generates, this is the job of filter(). Here \a s is a new
|
||||
/// state* that search() has just allocated (using
|
||||
/// twa_succ_iterator::dst()), and the return of this
|
||||
/// function should be a state* that does not need to be freed by
|
||||
/// search().
|
||||
///
|
||||
/// If you already have a map or a set which uses states as keys,
|
||||
/// you should probably arrange for filter() to return these keys,
|
||||
/// and destroy \a s. Otherwise you will have to define such a
|
||||
/// set, just to be able to destroy all the state* in a subclass.
|
||||
///
|
||||
/// This function can return 0 if the given state should not be
|
||||
/// explored.
|
||||
virtual const state* filter(const state* s) = 0;
|
||||
|
||||
/// \brief Whether a new transition completes a path.
|
||||
///
|
||||
/// This function is called immediately after each call to
|
||||
/// filter() that does not return 0.
|
||||
///
|
||||
/// \param step the source state (as returned by filter()), and the
|
||||
/// labels of the outgoing transition
|
||||
/// \param dest the destination state (as returned by filter())
|
||||
/// \return true iff a path that included this step should be accepted.
|
||||
///
|
||||
/// The search() algorithms stops as soon as match() returns true,
|
||||
/// and when this happens the list argument of search() is be
|
||||
/// augmented with the shortest past that ends with this
|
||||
/// transition.
|
||||
virtual bool match(twa_run::step& step, const state* dest) = 0;
|
||||
|
||||
/// \brief Append the resulting path to the resulting run.
|
||||
///
|
||||
/// This is called after match() has returned true, to append the
|
||||
/// resulting path to \a l. This seldom needs to be overridden,
|
||||
/// unless you do not want \a l to be updated (in which case an empty
|
||||
/// finalize() will do).
|
||||
virtual void finalize(const std::map<const state*, twa_run::step,
|
||||
state_ptr_less_than>& father,
|
||||
const twa_run::step& s,
|
||||
const state* start,
|
||||
twa_run::steps& l);
|
||||
|
||||
protected:
|
||||
const_twa_ptr a_; ///< The spot::tgba we are searching into.
|
||||
};
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "canonicalize.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef std::pair<spot::twa_graph::graph_t::edge_data_t, unsigned>
|
||||
edge_sig_t;
|
||||
|
||||
struct signature_t
|
||||
{
|
||||
std::vector<edge_sig_t> ingoing;
|
||||
std::vector<edge_sig_t> outgoing;
|
||||
unsigned classnum;
|
||||
|
||||
bool
|
||||
operator<(const signature_t& o) const
|
||||
{
|
||||
return ingoing != o.ingoing ? ingoing < o.ingoing :
|
||||
outgoing != o.outgoing ? outgoing < o.outgoing :
|
||||
classnum < o.classnum;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<signature_t, std::vector<unsigned>> sig2states_t;
|
||||
|
||||
static sig2states_t
|
||||
sig_to_states(spot::twa_graph_ptr aut, std::vector<unsigned>& state2class)
|
||||
{
|
||||
std::vector<signature_t> signature(aut->num_states(), signature_t());
|
||||
|
||||
for (auto& t : aut->edges())
|
||||
{
|
||||
signature[t.dst].ingoing.emplace_back(t.data(), state2class[t.src]);
|
||||
signature[t.src].outgoing.emplace_back(t.data(), state2class[t.dst]);
|
||||
}
|
||||
|
||||
sig2states_t sig2states;
|
||||
for (unsigned s = 0; s < aut->num_states(); ++s)
|
||||
{
|
||||
std::sort(signature[s].ingoing.begin(), signature[s].ingoing.end());
|
||||
std::sort(signature[s].outgoing.begin(), signature[s].outgoing.end());
|
||||
signature[s].classnum = state2class[s];
|
||||
sig2states[signature[s]].push_back(s);
|
||||
}
|
||||
|
||||
return sig2states;
|
||||
}
|
||||
}
|
||||
|
||||
namespace spot
|
||||
{
|
||||
twa_graph_ptr
|
||||
canonicalize(twa_graph_ptr aut)
|
||||
{
|
||||
std::vector<unsigned> state2class(aut->num_states(), 0);
|
||||
state2class[aut->get_init_state_number()] = 1;
|
||||
size_t distinct_classes = 2;
|
||||
sig2states_t sig2states = sig_to_states(aut, state2class);
|
||||
|
||||
while (sig2states.size() != distinct_classes &&
|
||||
sig2states.size() != aut->num_states())
|
||||
{
|
||||
distinct_classes = sig2states.size();
|
||||
|
||||
unsigned classnum = 0;
|
||||
for (auto& s: sig2states)
|
||||
{
|
||||
for (auto& state: s.second)
|
||||
state2class[state] = classnum;
|
||||
++classnum;
|
||||
}
|
||||
|
||||
sig2states = sig_to_states(aut, state2class);
|
||||
}
|
||||
|
||||
unsigned classnum = 0;
|
||||
for (auto& s: sig2states)
|
||||
for (auto& state: s.second)
|
||||
state2class[state] = classnum++;
|
||||
|
||||
auto& g = aut->get_graph();
|
||||
g.rename_states_(state2class);
|
||||
aut->set_init_state(state2class[aut->get_init_state_number()]);
|
||||
g.sort_edges_();
|
||||
g.chain_edges_();
|
||||
return aut;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2014 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_misc
|
||||
/// \brief Reorder the states and transitions of aut in a way that will be the
|
||||
/// same for every isomorphic automata.
|
||||
SPOT_API twa_graph_ptr canonicalize(twa_graph_ptr aut);
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 "twaalgos/cleanacc.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
twa_graph_ptr cleanup_acceptance_here(twa_graph_ptr aut)
|
||||
{
|
||||
auto& acc = aut->acc();
|
||||
if (acc.num_sets() == 0)
|
||||
return aut;
|
||||
|
||||
auto& c = aut->get_acceptance();
|
||||
acc_cond::mark_t used_in_cond = c.used_sets();
|
||||
|
||||
acc_cond::mark_t used_in_aut = 0U;
|
||||
for (auto& t: aut->edges())
|
||||
used_in_aut |= t.acc;
|
||||
|
||||
auto useful = used_in_aut & used_in_cond;
|
||||
|
||||
auto useless = acc.comp(useful);
|
||||
|
||||
if (!useless)
|
||||
return aut;
|
||||
|
||||
// Remove useless marks from the automaton
|
||||
for (auto& t: aut->edges())
|
||||
t.acc = t.acc.strip(useless);
|
||||
|
||||
// Remove useless marks from the acceptance condition
|
||||
aut->set_acceptance(useful.count(), c.strip(useless, true));
|
||||
|
||||
// This may in turn cause even more set to be unused, because of
|
||||
// some simplifications, so do it again.
|
||||
return cleanup_acceptance_here(aut);
|
||||
}
|
||||
|
||||
twa_graph_ptr cleanup_acceptance(const_twa_graph_ptr aut)
|
||||
{
|
||||
return cleanup_acceptance_here(make_twa_graph(aut,
|
||||
twa::prop_set::all()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Remove useless acceptance sets
|
||||
/// @{
|
||||
SPOT_API twa_graph_ptr
|
||||
cleanup_acceptance_here(twa_graph_ptr aut);
|
||||
|
||||
SPOT_API twa_graph_ptr
|
||||
cleanup_acceptance(const_twa_graph_ptr aut);
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "complement.hh"
|
||||
#include "sccinfo.hh"
|
||||
#include "complete.hh"
|
||||
#include "cleanacc.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
twa_graph_ptr
|
||||
dtwa_complement(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
// Simply complete the automaton, and complement its acceptance.
|
||||
auto res = cleanup_acceptance_here(complete(aut));
|
||||
res->set_acceptance(res->num_sets(), res->get_acceptance().complement());
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Complement a deterministic TωA
|
||||
///
|
||||
/// The automaton \a aut should be deterministic. It will be
|
||||
/// completed if it isn't already. In these conditions,
|
||||
/// complementing the automaton can be done by just complementing
|
||||
/// the acceptance condition.
|
||||
///
|
||||
/// In particular, this implies that an input that use
|
||||
/// generalized Büchi will be output as generalized co-Büchi.
|
||||
///
|
||||
/// Functions like to_generalized_buchi() or remove_fin() are
|
||||
/// frequently called after dtwa_complement() to obtain an easier
|
||||
/// acceptance condition (maybe at the cost of loosing determinism.)
|
||||
SPOT_API twa_graph_ptr
|
||||
dtwa_complement(const const_twa_graph_ptr& aut);
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "complete.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
unsigned complete_here(twa_graph_ptr aut)
|
||||
{
|
||||
// We do not use the initial state, but calling
|
||||
// get_init_state_number() may create it and change the number of
|
||||
// states. This has to be done before calling aut->num_states().
|
||||
unsigned init = aut->get_init_state_number();
|
||||
|
||||
unsigned n = aut->num_states();
|
||||
unsigned sink = -1U;
|
||||
|
||||
// UM is a pair (bool, mark). If the Boolean is false, the
|
||||
// acceptance is always satisfiable. Otherwise, MARK is an
|
||||
// example of unsatisfiable mark.
|
||||
auto um = aut->get_acceptance().unsat_mark();
|
||||
if (!um.first)
|
||||
{
|
||||
// We cannot safely complete an automaton if its
|
||||
// acceptance is always satisfiable.
|
||||
auto acc = aut->set_buchi();
|
||||
for (auto& t: aut->edge_vector())
|
||||
t.acc = acc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Loop over the states and seek a state that has only self
|
||||
// loops, and that is not accepting. This will be our sink
|
||||
// state. Note that we do not even have to ensure that the
|
||||
// state is complete as we will complete the whole automaton
|
||||
// in a second pass.
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
{
|
||||
bool selfloop = true;
|
||||
acc_cond::mark_t accsum = 0U;
|
||||
for (auto& t: aut->out(i))
|
||||
{
|
||||
if (t.dst != i) // Not a self-loop
|
||||
{
|
||||
selfloop = false;
|
||||
break;
|
||||
}
|
||||
accsum |= t.acc;
|
||||
}
|
||||
if (selfloop && !aut->acc().accepting(accsum))
|
||||
{
|
||||
// We have found a sink!
|
||||
sink = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned t = aut->num_edges();
|
||||
|
||||
// If the automaton is empty,
|
||||
// pretend that state
|
||||
if (t == 0)
|
||||
sink = init;
|
||||
|
||||
// Now complete all states (excluding any newly added the sink).
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
{
|
||||
bdd missingcond = bddtrue;
|
||||
acc_cond::mark_t acc = 0U;
|
||||
for (auto& t: aut->out(i))
|
||||
{
|
||||
missingcond -= t.cond;
|
||||
// FIXME: This is ugly.
|
||||
//
|
||||
// In case the automaton uses state-based acceptance, we
|
||||
// need to put the new edge in the same set as all
|
||||
// the other.
|
||||
//
|
||||
// In case the automaton uses edge-based acceptance,
|
||||
// it does not matter what acceptance set we put the new
|
||||
// edge into.
|
||||
//
|
||||
// So in both cases, we put the edge in the same
|
||||
// acceptance sets as the last outgoing edge of the
|
||||
// state.
|
||||
acc = t.acc;
|
||||
}
|
||||
// If the state has incomplete successors, we need to add a
|
||||
// edge to some sink state.
|
||||
if (missingcond != bddfalse)
|
||||
{
|
||||
// If we haven't found any sink, simply add one.
|
||||
if (sink == -1U)
|
||||
{
|
||||
sink = aut->new_state();
|
||||
aut->new_edge(sink, sink, bddtrue, um.second);
|
||||
}
|
||||
// In case the automaton use state-based acceptance, propagate
|
||||
// the acceptance of the first edge to the one we add.
|
||||
if (!aut->prop_state_acc())
|
||||
acc = 0U;
|
||||
aut->new_edge(i, sink, missingcond, acc);
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of any named property if the automaton changed.
|
||||
if (t < aut->num_edges())
|
||||
aut->release_named_properties();
|
||||
else
|
||||
assert(t == aut->num_edges());
|
||||
|
||||
return sink;
|
||||
}
|
||||
|
||||
twa_graph_ptr complete(const const_twa_ptr& aut)
|
||||
{
|
||||
auto res = make_twa_graph(aut, {
|
||||
true, // state based
|
||||
true, // inherently_weak
|
||||
true, // deterministic
|
||||
true, // stutter inv.
|
||||
});
|
||||
complete_here(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Complete a twa_graph in place.
|
||||
///
|
||||
/// If the TωA has no acceptance set, one will be added. The
|
||||
/// returned value is the number of the sink state (it can be a new
|
||||
/// state added for completion, or an existing non-accepting state
|
||||
/// that has been reused as sink state because it had no outgoing
|
||||
/// transitions apart from self-loops.)
|
||||
SPOT_API unsigned complete_here(twa_graph_ptr aut);
|
||||
|
||||
/// \brief Clone a twa and complete it.
|
||||
///
|
||||
/// If the twa has no acceptance set, one will be added.
|
||||
SPOT_API twa_graph_ptr complete(const const_twa_ptr& aut);
|
||||
}
|
||||
|
|
@ -1,345 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
#include "compsusp.hh"
|
||||
#include "sccfilter.hh"
|
||||
#include "sccinfo.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "ltl2tgba_fm.hh"
|
||||
#include "minimize.hh"
|
||||
#include "simulation.hh"
|
||||
#include "strength.hh"
|
||||
#include "tl/print.hh"
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include "tl/environment.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
typedef std::map<formula, bdd> formula_bdd_map;
|
||||
typedef std::vector<formula> vec;
|
||||
|
||||
// Rewrite the suspendable subformulae "s" of an LTL formula in
|
||||
// the form Gg where "g" is an atomic proposition representing
|
||||
// "s". At the same time, populate maps that associate "s" to "g"
|
||||
// and vice-versa.
|
||||
class ltl_suspender_visitor final
|
||||
{
|
||||
public:
|
||||
typedef std::map<formula, formula> fmap_t;
|
||||
ltl_suspender_visitor(fmap_t& g2s, fmap_t& a2o, bool oblig)
|
||||
: g2s_(g2s), a2o_(a2o), oblig_(oblig)
|
||||
{
|
||||
}
|
||||
|
||||
formula
|
||||
visit(formula f)
|
||||
{
|
||||
switch (op o = f.kind())
|
||||
{
|
||||
case op::Or:
|
||||
case op::And:
|
||||
{
|
||||
vec res;
|
||||
vec oblig;
|
||||
vec susp;
|
||||
unsigned mos = f.size();
|
||||
for (unsigned i = 0; i < mos; ++i)
|
||||
{
|
||||
formula c = f[i];
|
||||
if (c.is_boolean())
|
||||
res.push_back(c);
|
||||
else if (oblig_ && c.is_syntactic_obligation())
|
||||
oblig.push_back(c);
|
||||
else if (c.is_eventual() && c.is_universal())
|
||||
susp.push_back(c);
|
||||
else
|
||||
res.push_back(recurse(c));
|
||||
}
|
||||
if (!oblig.empty())
|
||||
{
|
||||
res.push_back(recurse(formula::multop(o, oblig)));
|
||||
}
|
||||
if (!susp.empty())
|
||||
{
|
||||
formula x = formula::multop(o, susp);
|
||||
// Rewrite 'x' as 'G"x"'
|
||||
formula g = recurse(x);
|
||||
if (o == op::And)
|
||||
{
|
||||
res.push_back(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
// res || susp -> (res && G![susp]) || G[susp])
|
||||
auto r = formula::multop(o, res);
|
||||
auto gn = formula::G(formula::Not(g[0]));
|
||||
return formula::Or({formula::And({r, gn}), g});
|
||||
}
|
||||
}
|
||||
return formula::multop(o, res);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return f.map([this](formula f)
|
||||
{
|
||||
return this->recurse(f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formula
|
||||
recurse(formula f)
|
||||
{
|
||||
formula res;
|
||||
if (f.is_boolean())
|
||||
return f;
|
||||
if (oblig_ && f.is_syntactic_obligation())
|
||||
{
|
||||
fmap_t::const_iterator i = assoc_.find(f);
|
||||
if (i != assoc_.end())
|
||||
return i->second;
|
||||
|
||||
std::ostringstream s;
|
||||
print_psl(s << "〈", f) << "〉";
|
||||
res = formula::ap(s.str());
|
||||
a2o_[res] = f;
|
||||
assoc_[f] = res;
|
||||
return res;
|
||||
}
|
||||
if (f.is_eventual() && f.is_universal())
|
||||
{
|
||||
fmap_t::const_iterator i = assoc_.find(f);
|
||||
if (i != assoc_.end())
|
||||
return formula::G(i->second);
|
||||
|
||||
std::ostringstream s;
|
||||
print_psl(s << '[', f) << "]$";
|
||||
res = formula::ap(s.str());
|
||||
g2s_[res] = f;
|
||||
assoc_[f] = res;
|
||||
return formula::G(res);
|
||||
}
|
||||
return visit(f);
|
||||
}
|
||||
|
||||
private:
|
||||
fmap_t& g2s_;
|
||||
fmap_t assoc_; // This one is only needed by the visitor.
|
||||
fmap_t& a2o_;
|
||||
bool oblig_;
|
||||
};
|
||||
|
||||
|
||||
typedef std::pair<const state*, const state*> state_pair;
|
||||
|
||||
typedef std::map<state_pair, unsigned> pair_map;
|
||||
typedef std::deque<state_pair> pair_queue;
|
||||
|
||||
static
|
||||
twa_graph_ptr
|
||||
susp_prod(const const_twa_ptr& left, formula f, bdd v)
|
||||
{
|
||||
bdd_dict_ptr dict = left->get_dict();
|
||||
auto right =
|
||||
iterated_simulations(scc_filter(ltl_to_tgba_fm(f, dict, true, true),
|
||||
false));
|
||||
|
||||
twa_graph_ptr res = make_twa_graph(dict);
|
||||
dict->register_all_variables_of(left, res);
|
||||
dict->register_all_variables_of(right, res);
|
||||
dict->unregister_variable(bdd_var(v), res);
|
||||
|
||||
const acc_cond& la = left->acc();
|
||||
const acc_cond& ra = right->acc();
|
||||
res->set_generalized_buchi(la.num_sets() + ra.num_sets());
|
||||
|
||||
acc_cond::mark_t radd = ra.all_sets();
|
||||
|
||||
pair_map seen;
|
||||
pair_queue todo;
|
||||
|
||||
state_pair p(left->get_init_state(), nullptr);
|
||||
const state* ris = right->get_init_state();
|
||||
p.second = ris;
|
||||
unsigned i = res->new_state();
|
||||
seen[p] = i;
|
||||
todo.push_back(p);
|
||||
res->set_init_state(i);
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
p = todo.front();
|
||||
todo.pop_front();
|
||||
const state* ls = p.first;
|
||||
const state* rs = p.second;
|
||||
int src = seen[p];
|
||||
|
||||
for (auto li: left->succ(ls))
|
||||
{
|
||||
state_pair d(li->dst(), ris);
|
||||
bdd lc = li->cond();
|
||||
|
||||
twa_succ_iterator* ri = nullptr;
|
||||
// Should we reset the right automaton?
|
||||
if ((lc & v) == lc)
|
||||
{
|
||||
// No.
|
||||
ri = right->succ_iter(rs);
|
||||
ri->first();
|
||||
}
|
||||
// Yes. Reset the right automaton.
|
||||
else
|
||||
{
|
||||
p.second = ris;
|
||||
}
|
||||
|
||||
// This loops over all the right edges
|
||||
// if RI is defined. Otherwise this just makes
|
||||
// one iteration as if the right automaton was
|
||||
// looping in state 0 with "true".
|
||||
while (!ri || !ri->done())
|
||||
{
|
||||
bdd cond = lc;
|
||||
acc_cond::mark_t racc = radd;
|
||||
if (ri)
|
||||
{
|
||||
cond = lc & ri->cond();
|
||||
// Skip incompatible edges.
|
||||
if (cond == bddfalse)
|
||||
{
|
||||
ri->next();
|
||||
continue;
|
||||
}
|
||||
d.second = ri->dst();
|
||||
racc = ri->acc();
|
||||
}
|
||||
|
||||
int dest;
|
||||
pair_map::const_iterator i = seen.find(d);
|
||||
if (i != seen.end()) // Is this an existing state?
|
||||
{
|
||||
dest = i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest = res->new_state();
|
||||
seen[d] = dest;
|
||||
todo.push_back(d);
|
||||
}
|
||||
|
||||
acc_cond::mark_t a =
|
||||
res->acc().join(la, li->acc(),
|
||||
ra, racc);
|
||||
res->new_edge(src, dest, bdd_exist(cond, v), a);
|
||||
|
||||
if (ri)
|
||||
ri->next();
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (ri)
|
||||
right->release_iter(ri);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
twa_graph_ptr
|
||||
compsusp(formula f, const bdd_dict_ptr& dict,
|
||||
bool no_wdba, bool no_simulation,
|
||||
bool early_susp, bool no_susp_product, bool wdba_smaller,
|
||||
bool oblig)
|
||||
{
|
||||
ltl_suspender_visitor::fmap_t g2s;
|
||||
ltl_suspender_visitor::fmap_t a2o;
|
||||
ltl_suspender_visitor v(g2s, a2o, oblig);
|
||||
formula g = v.recurse(f);
|
||||
|
||||
// Translate the patched formula, and remove useless SCCs.
|
||||
twa_graph_ptr res =
|
||||
scc_filter(ltl_to_tgba_fm(g, dict, true, true, false, false,
|
||||
nullptr, nullptr),
|
||||
false);
|
||||
|
||||
if (!no_wdba)
|
||||
{
|
||||
twa_graph_ptr min = minimize_obligation(res, g,
|
||||
nullptr, wdba_smaller);
|
||||
if (min != res)
|
||||
{
|
||||
res = min;
|
||||
no_simulation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!no_simulation)
|
||||
res = iterated_simulations(res);
|
||||
|
||||
// Create a map of suspended formulae to BDD variables.
|
||||
spot::formula_bdd_map susp;
|
||||
for (auto& it: g2s)
|
||||
{
|
||||
auto j = dict->var_map.find(it.first);
|
||||
// If no BDD variable of this suspended formula exist in the
|
||||
// BDD dict, it means the suspended subformulae was never
|
||||
// actually used in the automaton. Just skip it. FIXME: It
|
||||
// would be better if we had a way to check that the variable
|
||||
// is used in this automaton, and not in some automaton
|
||||
// (sharing the same dictionary.)
|
||||
if (j != dict->var_map.end())
|
||||
susp[it.second] = bdd_ithvar(j->second);
|
||||
}
|
||||
|
||||
// Remove suspendable formulae from non-accepting SCCs.
|
||||
bdd suspvars = bddtrue;
|
||||
for (formula_bdd_map::const_iterator i = susp.begin();
|
||||
i != susp.end(); ++i)
|
||||
suspvars &= i->second;
|
||||
|
||||
bdd allaccap = bddtrue; // set of atomic prop used in accepting SCCs.
|
||||
{
|
||||
scc_info si(res);
|
||||
|
||||
// Restrict suspvars to the set of suspension labels that occur
|
||||
// in accepting SCC.
|
||||
unsigned sn = si.scc_count();
|
||||
for (unsigned n = 0; n < sn; n++)
|
||||
if (si.is_accepting_scc(n))
|
||||
allaccap &= si.scc_ap_support(n);
|
||||
|
||||
bdd ignored = bdd_exist(suspvars, allaccap);
|
||||
suspvars = bdd_existcomp(suspvars, allaccap);
|
||||
res = scc_filter_susp(res, false, suspvars, ignored, early_susp, &si);
|
||||
}
|
||||
|
||||
// Do we need to synchronize any suspended formula?
|
||||
if (!susp.empty() && !no_susp_product)
|
||||
for (formula_bdd_map::const_iterator i = susp.begin();
|
||||
i != susp.end(); ++i)
|
||||
if ((allaccap & i->second) == allaccap)
|
||||
res = susp_prod(res, i->first, i->second);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "tl/formula.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
class bdd_dict;
|
||||
|
||||
/// \brief Compositional translation algorithm with resetable
|
||||
/// suspension.
|
||||
///
|
||||
/// Described in "Compositional Approach to Suspension and Other
|
||||
/// Improvements to LTL Translation", Tomáš Babiak, Thomas Badie,
|
||||
/// Alexandre Duret-Lutz, Mojmír Křetínský, Jan Strejček (SPIN'13).
|
||||
///
|
||||
/// If \a no_wdba or \a no_simulation is true, the corresponding
|
||||
/// operation is not performed on the skeleton automaton. If \a
|
||||
/// early_susp is true, then composition starts on the transition
|
||||
/// that enters the accepting SCC, not just in the SCC itself. If
|
||||
/// \a no_susp_product is true, then the composition is not
|
||||
/// performed and the skeleton automaton is returned for debugging.
|
||||
/// If \a wdba_smaller is true, then the WDBA-minimization of the
|
||||
/// skeleton is used only if it produces a smaller automaton.
|
||||
///
|
||||
/// Finally the \a oblig flag is a work in progress and should not
|
||||
/// be set to true.
|
||||
///
|
||||
/// This interface is subject to change, and clients aiming for
|
||||
/// long-term stability should better use the services of the
|
||||
/// spot::translator class instead.
|
||||
SPOT_API twa_graph_ptr
|
||||
compsusp(formula f, const bdd_dict_ptr& dict,
|
||||
bool no_wdba = false, bool no_simulation = false,
|
||||
bool early_susp = false, bool no_susp_product = false,
|
||||
bool wdba_smaller = false, bool oblig = false);
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2011, 2012, 2014, 2015 Laboratoire de Recherche
|
||||
// et Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 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 "copy.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "reachiter.hh"
|
||||
#include "dot.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class copy_iter: public tgba_reachable_iterator_depth_first
|
||||
{
|
||||
public:
|
||||
copy_iter(const const_twa_ptr& a, twa::prop_set p,
|
||||
bool preserve_names)
|
||||
: tgba_reachable_iterator_depth_first(a),
|
||||
out_(make_twa_graph(a->get_dict()))
|
||||
{
|
||||
out_->copy_acceptance_of(a);
|
||||
out_->copy_ap_of(a);
|
||||
out_->prop_copy(a, p);
|
||||
if (preserve_names)
|
||||
{
|
||||
names_ = new std::vector<std::string>;
|
||||
out_->set_named_prop("state-names", names_);
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
result()
|
||||
{
|
||||
return out_;
|
||||
}
|
||||
|
||||
virtual void
|
||||
process_state(const state* s, int n, twa_succ_iterator*)
|
||||
{
|
||||
unsigned ns = out_->new_state();
|
||||
if (names_)
|
||||
names_->emplace_back(aut_->format_state(s));
|
||||
assert(ns == static_cast<unsigned>(n) - 1);
|
||||
(void)ns;
|
||||
(void)n;
|
||||
}
|
||||
|
||||
virtual void
|
||||
process_link(const state*, int in,
|
||||
const state*, int out,
|
||||
const twa_succ_iterator* si)
|
||||
{
|
||||
out_->new_edge
|
||||
(in - 1, out - 1, si->cond(),
|
||||
si->acc());
|
||||
}
|
||||
|
||||
protected:
|
||||
twa_graph_ptr out_;
|
||||
std::vector<std::string>* names_ = nullptr;
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
twa_graph_ptr
|
||||
copy(const const_twa_ptr& aut, twa::prop_set p, bool preserve_names)
|
||||
{
|
||||
copy_iter di(aut, p, preserve_names);
|
||||
di.run();
|
||||
return di.result();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2012, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 2004, 2005 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 "misc/common.hh"
|
||||
#include "twa/fwd.hh"
|
||||
#include "twa/twa.hh"
|
||||
#include <vector>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_misc
|
||||
/// \brief Build an explicit automaton from all states of \a aut,
|
||||
///
|
||||
/// This works for using the abstract interface for automata
|
||||
SPOT_API twa_graph_ptr
|
||||
copy(const const_twa_ptr& aut, twa::prop_set p,
|
||||
bool preserve_names = false);
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 <iostream>
|
||||
#include "cycles.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
enumerate_cycles::enumerate_cycles(const scc_info& map)
|
||||
: aut_(map.get_aut()),
|
||||
info_(aut_->num_states(), aut_->num_states()),
|
||||
sm_(map)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
enumerate_cycles::nocycle(unsigned x, unsigned y)
|
||||
{
|
||||
// insert x in B(y)
|
||||
info_[y].b.push_back(x);
|
||||
// remove y from A(x)
|
||||
info_[x].del[y] = true;
|
||||
}
|
||||
|
||||
void
|
||||
enumerate_cycles::unmark(unsigned y)
|
||||
{
|
||||
std::vector<unsigned> q;
|
||||
q.push_back(y);
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
y = q.back();
|
||||
q.pop_back();
|
||||
|
||||
info_[y].mark = false;
|
||||
for (auto x: info_[y].b)
|
||||
{
|
||||
assert(info_[x].seen);
|
||||
// insert y in A(x)
|
||||
info_[x].del[y] = false;
|
||||
// unmark x recursively if marked
|
||||
if (info_[x].mark)
|
||||
q.push_back(x);
|
||||
}
|
||||
// empty B(y)
|
||||
info_[y].b.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
enumerate_cycles::push_state(unsigned s)
|
||||
{
|
||||
info_[s].mark = true;
|
||||
dfs_.emplace_back(s);
|
||||
}
|
||||
|
||||
void
|
||||
enumerate_cycles::run(unsigned scc)
|
||||
{
|
||||
bool keep_going = true;
|
||||
|
||||
{
|
||||
unsigned s = sm_.one_state_of(scc);
|
||||
info_[s].seen = true;
|
||||
push_state(s);
|
||||
}
|
||||
|
||||
while (keep_going && !dfs_.empty())
|
||||
{
|
||||
dfs_entry& cur = dfs_.back();
|
||||
if (cur.succ == 0)
|
||||
cur.succ = aut_->get_graph().state_storage(cur.s).succ;
|
||||
else
|
||||
cur.succ = aut_->edge_storage(cur.succ).next_succ;
|
||||
if (cur.succ)
|
||||
{
|
||||
// Explore one successor.
|
||||
|
||||
// Ignore those that are not on the SCC, or destination
|
||||
// that have been "virtually" deleted from A(v).
|
||||
unsigned s = aut_->edge_storage(cur.succ).dst;
|
||||
|
||||
if ((sm_.scc_of(s) != scc) || (info_[cur.s].del[s]))
|
||||
continue;
|
||||
|
||||
info_[s].seen = true;
|
||||
if (!info_[s].mark)
|
||||
{
|
||||
push_state(s);
|
||||
}
|
||||
else if (!info_[s].reach)
|
||||
{
|
||||
keep_going = cycle_found(s);
|
||||
cur.f = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nocycle(cur.s, s);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No more successors.
|
||||
bool f = cur.f;
|
||||
unsigned v = cur.s;
|
||||
|
||||
dfs_.pop_back();
|
||||
if (f)
|
||||
unmark(v);
|
||||
info_[v].reach = true;
|
||||
|
||||
// Update the predecessor in the stack if there is one.
|
||||
if (!dfs_.empty())
|
||||
{
|
||||
if (f)
|
||||
dfs_.back().f = true;
|
||||
else
|
||||
nocycle(dfs_.back().s, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Purge the dfs_ stack, in case we aborted because cycle_found()
|
||||
// returned false.
|
||||
dfs_.clear();
|
||||
}
|
||||
|
||||
bool
|
||||
enumerate_cycles::cycle_found(unsigned start)
|
||||
{
|
||||
dfs_stack::const_iterator i = dfs_.begin();
|
||||
while (i->s != start)
|
||||
++i;
|
||||
do
|
||||
{
|
||||
std::cout << i->s << ' ';
|
||||
++i;
|
||||
}
|
||||
while (i != dfs_.end());
|
||||
std::cout << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "sccinfo.hh"
|
||||
#include "misc/hash.hh"
|
||||
#include <deque>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Enumerate elementary cycles in a SCC.
|
||||
///
|
||||
/// This class implements a non-recursive version of the algorithm
|
||||
/// on page 170 of:
|
||||
/** \verbatim
|
||||
@Article{loizou.82.is,
|
||||
author = {George Loizou and Peter Thanisch},
|
||||
title = {Enumerating the Cycles of a Digraph: A New
|
||||
Preprocessing Strategy},
|
||||
journal = {Information Sciences},
|
||||
year = {1982},
|
||||
volume = {27},
|
||||
number = {3},
|
||||
pages = {163--182},
|
||||
month = aug
|
||||
}
|
||||
\endverbatim */
|
||||
/// (the additional preprocessings described later in that paper are
|
||||
/// not implemented).
|
||||
///
|
||||
/// It should be noted that although the above paper does not
|
||||
/// consider multiple arcs and self-loops in its definitions, the
|
||||
/// algorithm they present works as expected in these cases.
|
||||
///
|
||||
/// For our purpose an elementary cycle is a sequence of transitions
|
||||
/// that form a cycle and that visit a state at most once. We may
|
||||
/// have two cycles that visit the same states in the same order if
|
||||
/// some pair of states are connected by several transitions. Also
|
||||
/// A cycle may visit only one state if it is a self-loop.
|
||||
///
|
||||
/// We represent a cycle by a sequence of succ_iterator objects
|
||||
/// positioned on the transition contributing to the cycle. These
|
||||
/// succ_itertor are stored, along with their source state, in the
|
||||
/// dfs_ stack. Only the last portion of this stack may form a
|
||||
/// cycle.
|
||||
///
|
||||
/// Calling <code>run(n)</code> will enumerate all elementary cycles
|
||||
/// in SCC <code>n</code>. Each time an SCC is found, the method
|
||||
/// cycle_found(s) is called with the initial state s of the cycle:
|
||||
/// the cycle is constituted from all the states that are on the \c
|
||||
/// dfs_ stack after \c s (including \c s).
|
||||
///
|
||||
/// You should inherit from this class and redefine the
|
||||
/// cycle_found() method to perform any work you would like to do on
|
||||
/// the enumerated cycles. If cycle_found() returns false, the
|
||||
/// run() method will terminate. If it returns true, the run()
|
||||
/// method will search for the next elementary cycle and call
|
||||
/// cycle_found() again if it finds another cycle.
|
||||
class SPOT_API enumerate_cycles
|
||||
{
|
||||
protected:
|
||||
// Extra information required for the algorithm for each state.
|
||||
struct state_info
|
||||
{
|
||||
state_info(unsigned num)
|
||||
: seen(false), reach(false), mark(false), del(num)
|
||||
{
|
||||
}
|
||||
bool seen;
|
||||
// Whether the state has already left the stack at least once.
|
||||
bool reach;
|
||||
// set to true when the state current state w is stacked, and
|
||||
// reset either when the state is unstacked after having
|
||||
// contributed to a cycle, or when some state z that (1) w could
|
||||
// reach (even indirectly) without discovering a cycle, and (2)
|
||||
// that a contributed to a contributed to a cycle.
|
||||
bool mark;
|
||||
// Deleted successors (in the paper, states deleted from A(x))
|
||||
std::vector<bool> del;
|
||||
// Predecessors of the current states, that could not yet
|
||||
// contribute to a cycle.
|
||||
std::vector<unsigned> b;
|
||||
};
|
||||
|
||||
// The automaton we are working on.
|
||||
const_twa_graph_ptr aut_;
|
||||
// Store the state_info for all visited states.
|
||||
std::vector<state_info> info_;
|
||||
// The SCC map built for aut_.
|
||||
const scc_info& sm_;
|
||||
|
||||
// The DFS stack. Each entry contains a state, an iterator on the
|
||||
// transitions leaving that state, and a Boolean f indicating
|
||||
// whether this state as already contributed to a cycle (f is
|
||||
// updated when backtracking, so it should not be used by
|
||||
// cycle_found()).
|
||||
struct dfs_entry
|
||||
{
|
||||
unsigned s;
|
||||
unsigned succ = 0U;
|
||||
bool f = false;
|
||||
dfs_entry(unsigned s): s(s)
|
||||
{
|
||||
}
|
||||
};
|
||||
typedef std::vector<dfs_entry> dfs_stack;
|
||||
dfs_stack dfs_;
|
||||
|
||||
public:
|
||||
enumerate_cycles(const scc_info& map);
|
||||
virtual ~enumerate_cycles() {}
|
||||
|
||||
/// \brief Run in SCC scc, and call \a cycle_found() for any new
|
||||
/// elementary cycle found.
|
||||
///
|
||||
/// It is safe to call this method multiple times, for instance to
|
||||
/// enumerate the cycle of each SCC.
|
||||
void run(unsigned scc);
|
||||
|
||||
|
||||
/// \brief Called whenever a cycle was found.
|
||||
///
|
||||
/// The cycle uses all the states from the dfs stack, starting
|
||||
/// from the one labeled \a start. The iterators in the DFS stack
|
||||
/// are all pointing to the transition considered for the cycle.
|
||||
///
|
||||
/// This method is not const so you can modify private variables
|
||||
/// to your subclass, but it should definitely NOT modify the dfs
|
||||
/// stack or the tags map.
|
||||
///
|
||||
/// The default implementation, not very useful, will print the
|
||||
/// states in the cycle on std::cout.
|
||||
///
|
||||
/// This method method should return false iff no more cycles need
|
||||
/// should be enumerated by run().
|
||||
virtual bool cycle_found(unsigned start);
|
||||
|
||||
private:
|
||||
// add a new state to the dfs_ stack
|
||||
void push_state(unsigned s);
|
||||
// block the edge (x,y) because it cannot contribute to a new
|
||||
// cycle currently (sub-procedure from the paper)
|
||||
void nocycle(unsigned x, unsigned y);
|
||||
// unmark the state y (sub-procedure from the paper)
|
||||
void unmark(unsigned y);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,581 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2012, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "degen.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "misc/hash.hh"
|
||||
#include "misc/hashfunc.hh"
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "twaalgos/sccinfo.hh"
|
||||
#include "twa/bddprint.hh"
|
||||
|
||||
//#define DEGEN_DEBUG
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// A state in the degenalized automaton corresponds to a state in
|
||||
// the TGBA associated to a level. The level is just an index in
|
||||
// the list of acceptance sets.
|
||||
typedef std::pair<unsigned, unsigned> degen_state;
|
||||
|
||||
struct degen_state_hash
|
||||
{
|
||||
size_t
|
||||
operator()(const degen_state& s) const
|
||||
{
|
||||
return wang32_hash(s.first ^ wang32_hash(s.second));
|
||||
}
|
||||
};
|
||||
|
||||
// Associate the degeneralized state to its number.
|
||||
typedef std::unordered_map<degen_state, int, degen_state_hash> ds2num_map;
|
||||
|
||||
// Queue of state to be processed.
|
||||
typedef std::deque<degen_state> queue_t;
|
||||
|
||||
// Acceptance set common to all outgoing edges (of the same
|
||||
// SCC -- we do not care about the other) of some state.
|
||||
class outgoing_acc
|
||||
{
|
||||
const_twa_graph_ptr a_;
|
||||
typedef std::tuple<acc_cond::mark_t,
|
||||
acc_cond::mark_t,
|
||||
bool> cache_entry;
|
||||
std::vector<cache_entry> cache_;
|
||||
const scc_info* sm_;
|
||||
|
||||
void fill_cache(unsigned s)
|
||||
{
|
||||
unsigned s1 = sm_ ? sm_->scc_of(s) : 0;
|
||||
acc_cond::mark_t common = a_->acc().all_sets();
|
||||
acc_cond::mark_t union_ = 0U;
|
||||
bool has_acc_self_loop = false;
|
||||
bool seen = false;
|
||||
for (auto& t: a_->out(s))
|
||||
{
|
||||
// Ignore edges that leave the SCC of s.
|
||||
unsigned d = t.dst;
|
||||
unsigned s2 = sm_ ? sm_->scc_of(d) : 0;
|
||||
if (s2 != s1)
|
||||
continue;
|
||||
|
||||
common &= t.acc;
|
||||
union_ |= t.acc;
|
||||
|
||||
// an accepting self-loop?
|
||||
has_acc_self_loop |= (t.dst == s) && a_->acc().accepting(t.acc);
|
||||
seen = true;
|
||||
}
|
||||
if (!seen)
|
||||
common = 0U;
|
||||
cache_[s] = std::make_tuple(common, union_, has_acc_self_loop);
|
||||
}
|
||||
|
||||
public:
|
||||
outgoing_acc(const const_twa_graph_ptr& a, const scc_info* sm):
|
||||
a_(a), cache_(a->num_states()), sm_(sm)
|
||||
{
|
||||
unsigned n = a->num_states();
|
||||
for (unsigned s = 0; s < n; ++s)
|
||||
fill_cache(s);
|
||||
}
|
||||
|
||||
// Intersection of all outgoing acceptance sets
|
||||
acc_cond::mark_t common_acc(unsigned s)
|
||||
{
|
||||
assert(s < cache_.size());
|
||||
return std::get<0>(cache_[s]);
|
||||
}
|
||||
|
||||
// Union of all outgoing acceptance sets
|
||||
acc_cond::mark_t union_acc(unsigned s)
|
||||
{
|
||||
assert(s < cache_.size());
|
||||
return std::get<1>(cache_[s]);
|
||||
}
|
||||
|
||||
// Has an accepting self-loop
|
||||
bool has_acc_selfloop(unsigned s)
|
||||
{
|
||||
assert(s < cache_.size());
|
||||
return std::get<2>(cache_[s]);
|
||||
}
|
||||
};
|
||||
|
||||
// Order of accepting sets (for one SCC)
|
||||
class acc_order
|
||||
{
|
||||
std::vector<unsigned> order_;
|
||||
acc_cond::mark_t found_;
|
||||
|
||||
public:
|
||||
unsigned
|
||||
next_level(int slevel, acc_cond::mark_t set, bool skip_levels)
|
||||
{
|
||||
// Update the order with any new set we discover
|
||||
if (auto newsets = set - found_)
|
||||
{
|
||||
newsets.fill(std::back_inserter(order_));
|
||||
found_ |= newsets;
|
||||
}
|
||||
|
||||
unsigned next = slevel;
|
||||
while (next < order_.size() && set.has(order_[next]))
|
||||
{
|
||||
++next;
|
||||
if (!skip_levels)
|
||||
break;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
void
|
||||
print(int scc)
|
||||
{
|
||||
std::cout << "Order_" << scc << ":\t";
|
||||
for (auto i: order_)
|
||||
std::cout << i << ", ";
|
||||
std::cout << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
// Accepting order for each SCC
|
||||
class scc_orders
|
||||
{
|
||||
std::map<int, acc_order> orders_;
|
||||
bool skip_levels_;
|
||||
|
||||
public:
|
||||
scc_orders(bool skip_levels):
|
||||
skip_levels_(skip_levels)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned
|
||||
next_level(int scc, int slevel, acc_cond::mark_t set)
|
||||
{
|
||||
return orders_[scc].next_level(slevel, set, skip_levels_);
|
||||
}
|
||||
|
||||
void
|
||||
print()
|
||||
{
|
||||
std::map<int, acc_order>::iterator i;
|
||||
for (i = orders_.begin(); i != orders_.end(); i++)
|
||||
i->second.print(i->first);
|
||||
}
|
||||
};
|
||||
|
||||
template<bool want_sba>
|
||||
twa_graph_ptr
|
||||
degeneralize_aux(const const_twa_graph_ptr& a, bool use_z_lvl,
|
||||
bool use_cust_acc_orders, int use_lvl_cache,
|
||||
bool skip_levels, bool ignaccsl)
|
||||
{
|
||||
if (!a->acc().is_generalized_buchi())
|
||||
throw std::runtime_error
|
||||
("degeneralize() can only work with generalized Büchi acceptance");
|
||||
|
||||
bool use_scc = use_lvl_cache || use_cust_acc_orders || use_z_lvl;
|
||||
|
||||
bdd_dict_ptr dict = a->get_dict();
|
||||
|
||||
// The result automaton is an SBA.
|
||||
auto res = make_twa_graph(dict);
|
||||
res->copy_ap_of(a);
|
||||
res->set_buchi();
|
||||
if (want_sba)
|
||||
res->prop_state_acc(true);
|
||||
// Preserve determinism, weakness, and stutter-invariance
|
||||
res->prop_copy(a, { false, true, true, true });
|
||||
|
||||
// Create an order of acceptance conditions. Each entry in this
|
||||
// vector correspond to an acceptance set. Each index can
|
||||
// be used as a level in degen_state to indicate the next expected
|
||||
// acceptance set. Level order.size() is a special level used to
|
||||
// denote accepting states.
|
||||
std::vector<unsigned> order;
|
||||
{
|
||||
// FIXME: revisit this comment once everything compiles again.
|
||||
//
|
||||
// The order is arbitrary, but it turns out that using push_back
|
||||
// instead of push_front often gives better results because
|
||||
// acceptance sets at the beginning if the cycle are more often
|
||||
// used in the automaton. (This surprising fact is probably
|
||||
// related to the order in which we declare the BDD variables
|
||||
// during the translation.)
|
||||
unsigned n = a->num_sets();
|
||||
for (unsigned i = n; i > 0; --i)
|
||||
order.push_back(i - 1);
|
||||
}
|
||||
|
||||
// Initialize scc_orders
|
||||
scc_orders orders(skip_levels);
|
||||
|
||||
// and vice-versa.
|
||||
ds2num_map ds2num;
|
||||
|
||||
// This map is used to find edges that go to the same
|
||||
// destination with the same acceptance. The integer key is
|
||||
// (dest*2+acc) where dest is the destination state number, and
|
||||
// acc is 1 iff the edge is accepting. The source
|
||||
// is always that of the current iteration.
|
||||
typedef std::map<int, unsigned> tr_cache_t;
|
||||
tr_cache_t tr_cache;
|
||||
|
||||
// Read this early, because it might create a state if the
|
||||
// automaton is empty.
|
||||
degen_state s(a->get_init_state_number(), 0);
|
||||
|
||||
// State->level cache
|
||||
std::vector<std::pair<unsigned, bool>> lvl_cache(a->num_states());
|
||||
|
||||
// Compute SCCs in order to use any optimization.
|
||||
scc_info* m = nullptr;
|
||||
if (use_scc)
|
||||
m = new scc_info(a);
|
||||
|
||||
// Cache for common outgoing acceptances.
|
||||
outgoing_acc outgoing(a, m);
|
||||
|
||||
queue_t todo;
|
||||
|
||||
// As a heuristic for building SBA, if the initial state has at
|
||||
// least one accepting self-loop, start the degeneralization on
|
||||
// the accepting level.
|
||||
if (want_sba && !ignaccsl && outgoing.has_acc_selfloop(s.first))
|
||||
s.second = order.size();
|
||||
// Otherwise, check for acceptance conditions common to all
|
||||
// outgoing edges, and assume we have already seen these and
|
||||
// start on the associated level.
|
||||
if (s.second == 0)
|
||||
{
|
||||
auto set = outgoing.common_acc(s.first);
|
||||
if (use_cust_acc_orders)
|
||||
s.second = orders.next_level(m->initial(), s.second, set);
|
||||
else
|
||||
while (s.second < order.size()
|
||||
&& set.has(order[s.second]))
|
||||
{
|
||||
++s.second;
|
||||
if (!skip_levels)
|
||||
break;
|
||||
}
|
||||
// There is not accepting level for TBA, let reuse level 0.
|
||||
if (!want_sba && s.second == order.size())
|
||||
s.second = 0;
|
||||
}
|
||||
|
||||
ds2num[s] = res->new_state();
|
||||
todo.push_back(s);
|
||||
|
||||
// If use_lvl_cache is on insert initial state to level cache
|
||||
// Level cache stores first encountered level for each state.
|
||||
// When entering an SCC first the lvl_cache is checked.
|
||||
// If such state exists level from chache is used.
|
||||
// If not, a new level (starting with 0) is computed.
|
||||
if (use_lvl_cache)
|
||||
lvl_cache[s.first] = std::make_pair(s.second, true);
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
s = todo.front();
|
||||
todo.pop_front();
|
||||
int src = ds2num[s];
|
||||
unsigned slevel = s.second;
|
||||
|
||||
// If we have a state on the last level, it should be accepting.
|
||||
bool is_acc = slevel == order.size();
|
||||
// On the accepting level, start again from level 0.
|
||||
if (want_sba && is_acc)
|
||||
slevel = 0;
|
||||
|
||||
// Check SCC for state s
|
||||
int s_scc = -1;
|
||||
if (use_scc)
|
||||
s_scc = m->scc_of(s.first);
|
||||
|
||||
for (auto& i: a->out(s.first))
|
||||
{
|
||||
degen_state d(i.dst, 0);
|
||||
|
||||
// Check whether the target SCC is accepting
|
||||
bool is_scc_acc;
|
||||
int scc;
|
||||
if (use_scc)
|
||||
{
|
||||
scc = m->scc_of(d.first);
|
||||
is_scc_acc = m->is_accepting_scc(scc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have no SCC information, treat all SCCs as
|
||||
// accepting.
|
||||
scc = -1;
|
||||
is_scc_acc = true;
|
||||
}
|
||||
|
||||
// The old level is slevel. What should be the new one?
|
||||
auto acc = i.acc;
|
||||
auto otheracc = outgoing.common_acc(d.first);
|
||||
|
||||
if (want_sba && is_acc)
|
||||
{
|
||||
// Ignore the last expected acceptance set (the value of
|
||||
// prev below) if it is common to all other outgoing
|
||||
// edges (of the current state) AND if it is not
|
||||
// used by any outgoing edge of the destination
|
||||
// state.
|
||||
//
|
||||
// 1) It's correct to do that, because this acceptance
|
||||
// set is common to other outgoing edges.
|
||||
// Therefore if we make a cycle to this state we
|
||||
// will eventually see that acceptance set thanks
|
||||
// to the "pulling" of the common acceptance sets
|
||||
// of the destination state (d.first).
|
||||
//
|
||||
// 2) It's also desirable because it makes the
|
||||
// degeneralization idempotent (up to a renaming
|
||||
// of states). Consider the following automaton
|
||||
// where 1 is initial and => marks accepting
|
||||
// edges: 1=>1, 1=>2, 2->2, 2->1. This is
|
||||
// already an SBA, with 1 as accepting state.
|
||||
// However if you try degeralize it without
|
||||
// ignoring *prev, you'll get two copies of state
|
||||
// 2, depending on whether we reach it using 1=>2
|
||||
// or from 2->2. If this example was not clear,
|
||||
// play with the "degenid.test" test case.
|
||||
//
|
||||
// 3) Ignoring all common acceptance sets would also
|
||||
// be correct, but it would make the
|
||||
// degeneralization produce larger automata in some
|
||||
// cases. The current condition to ignore only one
|
||||
// acceptance set if is this not used by the next
|
||||
// state is a heuristic that is compatible with
|
||||
// point 2) above while not causing more states to
|
||||
// be generated in our benchmark of 188 formulae
|
||||
// from the literature.
|
||||
if (!order.empty())
|
||||
{
|
||||
unsigned prev = order.size() - 1;
|
||||
auto common = outgoing.common_acc(s.first);
|
||||
if (common.has(order[prev]))
|
||||
{
|
||||
auto u = outgoing.union_acc(d.first);
|
||||
if (!u.has(order[prev]))
|
||||
acc -= a->acc().mark(order[prev]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// A edge in the SLEVEL acceptance set should
|
||||
// be directed to the next acceptance set. If the
|
||||
// current edge is also in the next acceptance
|
||||
// set, then go to the one after, etc.
|
||||
//
|
||||
// See Denis Oddoux's PhD thesis for a nice
|
||||
// explanation (in French).
|
||||
// @PhDThesis{ oddoux.03.phd,
|
||||
// author = {Denis Oddoux},
|
||||
// title = {Utilisation des automates alternants pour un
|
||||
// model-checking efficace des logiques
|
||||
// temporelles lin{\'e}aires.},
|
||||
// school = {Universit{\'e}e Paris 7},
|
||||
// year = {2003},
|
||||
// address= {Paris, France},
|
||||
// month = {December}
|
||||
// }
|
||||
if (is_scc_acc)
|
||||
{
|
||||
// If lvl_cache is used and switching SCCs, use level
|
||||
// from cache
|
||||
if (use_lvl_cache && s_scc != scc
|
||||
&& lvl_cache[d.first].second)
|
||||
{
|
||||
d.second = lvl_cache[d.first].first;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Complete (or replace) the acceptance sets of
|
||||
// this link with the acceptance sets common to
|
||||
// all edges leaving the destination state.
|
||||
if (s_scc == scc)
|
||||
acc |= otheracc;
|
||||
else
|
||||
acc = otheracc;
|
||||
|
||||
// If use_z_lvl is on, start with level zero 0 when
|
||||
// switching SCCs
|
||||
unsigned next = (!use_z_lvl || s_scc == scc) ? slevel : 0;
|
||||
|
||||
// If using custom acc orders, get next level
|
||||
// for this scc
|
||||
if (use_cust_acc_orders)
|
||||
{
|
||||
d.second = orders.next_level(scc, next, acc);
|
||||
}
|
||||
// Else compute level according the global acc order
|
||||
else
|
||||
{
|
||||
// As a heuristic, if we enter the SCC on a
|
||||
// state that has at least one accepting
|
||||
// self-loop, start the degeneralization on
|
||||
// the accepting level.
|
||||
if (s_scc != scc
|
||||
&& !ignaccsl
|
||||
&& outgoing.has_acc_selfloop(d.first))
|
||||
{
|
||||
d.second = order.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Consider both the current acceptance
|
||||
// sets, and the acceptance sets common to
|
||||
// the outgoing edges of the
|
||||
// destination state. But don't do
|
||||
// that if the state is accepting and we
|
||||
// are not skipping levels.
|
||||
if (skip_levels || !is_acc)
|
||||
while (next < order.size()
|
||||
&& acc.has(order[next]))
|
||||
{
|
||||
++next;
|
||||
if (!skip_levels)
|
||||
break;
|
||||
}
|
||||
d.second = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In case we are building a TBA is_acc has to be
|
||||
// set differently for each edge, and
|
||||
// we do not need to stay use final level.
|
||||
if (!want_sba)
|
||||
{
|
||||
is_acc = d.second == order.size();
|
||||
if (is_acc) // The edge is accepting
|
||||
{
|
||||
d.second = 0; // Make it go to the first level.
|
||||
// Skip levels as much as possible.
|
||||
if (!a->acc().accepting(acc) && !skip_levels)
|
||||
{
|
||||
if (use_cust_acc_orders)
|
||||
{
|
||||
d.second = orders.next_level(scc, d.second, acc);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (d.second < order.size() &&
|
||||
acc.has(order[d.second]))
|
||||
++d.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Have we already seen this destination?
|
||||
int dest;
|
||||
ds2num_map::const_iterator di = ds2num.find(d);
|
||||
if (di != ds2num.end())
|
||||
{
|
||||
dest = di->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest = res->new_state();
|
||||
ds2num[d] = dest;
|
||||
todo.push_back(d);
|
||||
// Insert new state to cache
|
||||
|
||||
if (use_lvl_cache)
|
||||
{
|
||||
auto lvl = d.second;
|
||||
if (lvl_cache[d.first].second)
|
||||
{
|
||||
if (use_lvl_cache == 3)
|
||||
lvl = std::max(lvl_cache[d.first].first, lvl);
|
||||
else if (use_lvl_cache == 2)
|
||||
lvl = std::min(lvl_cache[d.first].first, lvl);
|
||||
}
|
||||
lvl_cache[d.first] = std::make_pair(lvl, true);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned& t = tr_cache[dest * 2 + is_acc];
|
||||
|
||||
if (t == 0) // Create edge.
|
||||
t = res->new_acc_edge(src, dest, i.cond, is_acc);
|
||||
else // Update existing edge.
|
||||
res->edge_data(t).cond |= i.cond;
|
||||
}
|
||||
tr_cache.clear();
|
||||
}
|
||||
|
||||
#ifdef DEGEN_DEBUG
|
||||
std::cout << "Orig. order: \t";
|
||||
for (auto i: order)
|
||||
{
|
||||
std::cout << i << ", ";
|
||||
}
|
||||
std::cout << '\n';
|
||||
orders.print();
|
||||
#endif
|
||||
|
||||
delete m;
|
||||
|
||||
res->merge_edges();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
degeneralize(const const_twa_graph_ptr& a,
|
||||
bool use_z_lvl, bool use_cust_acc_orders,
|
||||
int use_lvl_cache, bool skip_levels, bool ignaccsl)
|
||||
{
|
||||
// If this already a degeneralized digraph, there is nothing we
|
||||
// can improve.
|
||||
if (a->is_sba())
|
||||
return std::const_pointer_cast<twa_graph>(a);
|
||||
|
||||
return degeneralize_aux<true>(a, use_z_lvl, use_cust_acc_orders,
|
||||
use_lvl_cache, skip_levels, ignaccsl);
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
degeneralize_tba(const const_twa_graph_ptr& a,
|
||||
bool use_z_lvl, bool use_cust_acc_orders,
|
||||
int use_lvl_cache, bool skip_levels, bool ignaccsl)
|
||||
{
|
||||
// If this already a degeneralized digraph, there is nothing we
|
||||
// can improve.
|
||||
if (a->acc().is_buchi())
|
||||
return std::const_pointer_cast<twa_graph>(a);
|
||||
|
||||
return degeneralize_aux<false>(a, use_z_lvl, use_cust_acc_orders,
|
||||
use_lvl_cache, skip_levels, ignaccsl);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2012, 2013, 2014 2015, Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_misc
|
||||
/// \brief Degeneralize a spot::tgba into an equivalent sba with
|
||||
/// only one acceptance condition.
|
||||
///
|
||||
/// This algorithms will build a new explicit automaton that has
|
||||
/// at most (N+1) times the number of states of the original automaton.
|
||||
///
|
||||
/// When \a use_z_lvl is set, the level of the degeneralized
|
||||
/// automaton is reset everytime an SCC is exited. If \a
|
||||
/// use_cust_acc_orders is set, the degeneralization will compute a
|
||||
/// custom acceptance order for each SCC (this option is disabled by
|
||||
/// default because our benchmarks show that it usually does more
|
||||
/// harm than good). If \a use_lvl_cache is set, everytime an SCC
|
||||
/// is entered on a state that as already been associated to some
|
||||
/// level elsewhere, reuse that level (set it to 2 to keep the
|
||||
/// smallest number, 3 to keep the largest level, and 1 to keep the
|
||||
/// first level found). If \a ignaccsl is set, we do not directly
|
||||
/// jump to the accepting level if the entering state has an
|
||||
/// accepting self-loop.
|
||||
///
|
||||
/// Any of these three options will cause the SCCs of the automaton
|
||||
/// \a a to be computed prior to its actual degeneralization.
|
||||
///
|
||||
/// The degeneralize_tba() variant produce a degeneralized automaton
|
||||
/// with transition-based acceptance.
|
||||
/// \@{
|
||||
SPOT_API twa_graph_ptr
|
||||
degeneralize(const const_twa_graph_ptr& a, bool use_z_lvl = true,
|
||||
bool use_cust_acc_orders = false,
|
||||
int use_lvl_cache = 1,
|
||||
bool skip_levels = true,
|
||||
bool ignaccsl = false);
|
||||
|
||||
SPOT_API twa_graph_ptr
|
||||
degeneralize_tba(const const_twa_graph_ptr& a, bool use_z_lvl = true,
|
||||
bool use_cust_acc_orders = false,
|
||||
int use_lvl_cache = 1,
|
||||
bool skip_levels = true,
|
||||
bool ignaccsl = false);
|
||||
/// \@}
|
||||
}
|
||||
|
|
@ -1,575 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2012, 2014, 2015 Laboratoire de Recherche et
|
||||
// Developpement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 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 <ostream>
|
||||
#include <stdexcept>
|
||||
#include "twa/twagraph.hh"
|
||||
#include "dot.hh"
|
||||
#include "twa/bddprint.hh"
|
||||
#include "reachiter.hh"
|
||||
#include "misc/escape.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "twa/formula2bdd.hh"
|
||||
#include "twaalgos/copy.hh"
|
||||
#include "twaalgos/sccinfo.hh"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr int MAX_BULLET = 20;
|
||||
|
||||
class dotty_output
|
||||
{
|
||||
std::ostream& os_;
|
||||
bool opt_force_acc_trans_ = false;
|
||||
bool opt_horizontal_ = true;
|
||||
bool opt_name_ = false;
|
||||
enum { ShapeAuto = 0, ShapeCircle, ShapeEllipse }
|
||||
opt_shape_ = ShapeAuto;
|
||||
bool opt_show_acc_ = false;
|
||||
bool mark_states_ = false;
|
||||
bool opt_scc_ = false;
|
||||
bool opt_html_labels_ = false;
|
||||
const_twa_graph_ptr aut_;
|
||||
std::vector<std::string>* sn_ = nullptr;
|
||||
std::vector<std::pair<unsigned, unsigned>>* sprod_ = nullptr;
|
||||
std::string* name_ = nullptr;
|
||||
acc_cond::mark_t inf_sets_ = 0U;
|
||||
acc_cond::mark_t fin_sets_ = 0U;
|
||||
bool opt_rainbow = false;
|
||||
bool opt_bullet = false;
|
||||
bool opt_bullet_but_buchi = false;
|
||||
bool opt_all_bullets = false;
|
||||
bool opt_numbered_trans = false;
|
||||
bool opt_want_state_names_ = true;
|
||||
unsigned opt_shift_sets_ = 0;
|
||||
std::string opt_font_;
|
||||
|
||||
const char* const palette9[9] =
|
||||
{
|
||||
"#5DA5DA", /* blue */
|
||||
"#F17CB0", /* pink */
|
||||
"#FAA43A", /* orange */
|
||||
"#B276B2", /* purple */
|
||||
"#60BD68", /* green */
|
||||
"#F15854", /* red */
|
||||
"#B2912F", /* brown */
|
||||
"#4D4D4D", /* gray */
|
||||
"#DECF3F", /* yellow */
|
||||
};
|
||||
const char*const* palette = palette9;
|
||||
int palette_mod = 9;
|
||||
|
||||
public:
|
||||
|
||||
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 '+':
|
||||
{
|
||||
char* end;
|
||||
opt_shift_sets_ = strtoul(options, &end, 10);
|
||||
if (options == end)
|
||||
throw std::runtime_error
|
||||
("missing number after '+' in print_dot() options");
|
||||
options = end;
|
||||
break;
|
||||
}
|
||||
case '1':
|
||||
opt_want_state_names_ = false;
|
||||
break;
|
||||
case 'a':
|
||||
opt_show_acc_ = true;
|
||||
break;
|
||||
case 'b':
|
||||
opt_bullet = true;
|
||||
opt_bullet_but_buchi = false;
|
||||
break;
|
||||
case 'B':
|
||||
opt_bullet = true;
|
||||
opt_bullet_but_buchi = true;
|
||||
break;
|
||||
case 'c':
|
||||
opt_shape_ = ShapeCircle;
|
||||
break;
|
||||
case 'e':
|
||||
opt_shape_ = ShapeEllipse;
|
||||
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 'h':
|
||||
opt_horizontal_ = true;
|
||||
break;
|
||||
case 'n':
|
||||
opt_name_ = true;
|
||||
break;
|
||||
case 'N':
|
||||
opt_name_ = false;
|
||||
break;
|
||||
case 'o':
|
||||
opt_numbered_trans = true;
|
||||
break;
|
||||
case 'r':
|
||||
opt_html_labels_ = true;
|
||||
opt_rainbow = true;
|
||||
break;
|
||||
case 'R':
|
||||
opt_html_labels_ = true;
|
||||
opt_rainbow = false;
|
||||
break;
|
||||
case 's':
|
||||
opt_scc_ = true;
|
||||
break;
|
||||
case 'v':
|
||||
opt_horizontal_ = false;
|
||||
break;
|
||||
case 't':
|
||||
opt_force_acc_trans_ = true;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error
|
||||
(std::string("unknown option for print_dot(): ") + c);
|
||||
}
|
||||
}
|
||||
|
||||
dotty_output(std::ostream& os, const char* options)
|
||||
: os_(os)
|
||||
{
|
||||
parse_opts(options ? options : ".");
|
||||
}
|
||||
|
||||
void
|
||||
output_set(std::ostream& os, int v) const
|
||||
{
|
||||
v += opt_shift_sets_;
|
||||
if (opt_bullet && (v >= 0) & (v <= MAX_BULLET))
|
||||
{
|
||||
static const char* const tab[MAX_BULLET + 1] = {
|
||||
"⓿", "❶", "❷", "❸",
|
||||
"❹", "❺", "❻", "❼",
|
||||
"❽", "❾", "❿", "⓫",
|
||||
"⓬", "⓭", "⓮", "⓯",
|
||||
"⓰", "⓱", "⓲", "⓳",
|
||||
"⓴",
|
||||
};
|
||||
os << tab[v];
|
||||
}
|
||||
else
|
||||
{
|
||||
os << v;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
output_set(acc_cond::mark_t a) const
|
||||
{
|
||||
if (!opt_all_bullets)
|
||||
os_ << '{';
|
||||
const char* space = "";
|
||||
for (auto v: a.sets())
|
||||
{
|
||||
if (!opt_all_bullets)
|
||||
os_ << space;
|
||||
output_set(os_, v);
|
||||
space = ",";
|
||||
}
|
||||
if (!opt_all_bullets)
|
||||
os_ << '}';
|
||||
}
|
||||
|
||||
const char*
|
||||
html_set_color(int v) const
|
||||
{
|
||||
if (opt_rainbow)
|
||||
return palette[(v + opt_shift_sets_) % palette_mod];
|
||||
// Color according to Fin/Inf
|
||||
if (inf_sets_.has(v))
|
||||
{
|
||||
if (fin_sets_.has(v))
|
||||
return palette[2];
|
||||
else
|
||||
return palette[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return palette[1];
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
output_html_set_aux(std::ostream& os, int v) const
|
||||
{
|
||||
os << "<font color=\"" << html_set_color(v) << "\">";
|
||||
output_set(os, v);
|
||||
os << "</font>";
|
||||
}
|
||||
|
||||
void
|
||||
output_html_set(int v) const
|
||||
{
|
||||
output_html_set_aux(os_, v);
|
||||
}
|
||||
|
||||
void
|
||||
output_html_set(acc_cond::mark_t a) const
|
||||
{
|
||||
if (!opt_all_bullets)
|
||||
os_ << '{';
|
||||
const char* space = "";
|
||||
for (auto v: a.sets())
|
||||
{
|
||||
if (!opt_all_bullets)
|
||||
os_ << space;
|
||||
output_html_set(v);
|
||||
space = ",";
|
||||
}
|
||||
if (!opt_all_bullets)
|
||||
os_ << '}';
|
||||
}
|
||||
|
||||
void
|
||||
start()
|
||||
{
|
||||
if (opt_html_labels_)
|
||||
std::tie(inf_sets_, fin_sets_) =
|
||||
aut_->get_acceptance().used_inf_fin_sets();
|
||||
if (opt_bullet && aut_->num_sets() <= MAX_BULLET)
|
||||
opt_all_bullets = true;
|
||||
os_ << "digraph G {\n";
|
||||
if (opt_horizontal_)
|
||||
os_ << " rankdir=LR\n";
|
||||
if (name_ || opt_show_acc_)
|
||||
{
|
||||
if (!opt_html_labels_)
|
||||
{
|
||||
os_ << " label=\"";
|
||||
if (name_)
|
||||
{
|
||||
escape_str(os_, *name_);
|
||||
if (opt_show_acc_)
|
||||
os_ << "\\n";
|
||||
}
|
||||
if (opt_show_acc_)
|
||||
aut_->get_acceptance().to_text
|
||||
(os_, [this](std::ostream& os, int v)
|
||||
{
|
||||
this->output_set(os, v);
|
||||
});
|
||||
os_ << "\"\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
os_ << " label=<";
|
||||
if (name_)
|
||||
{
|
||||
escape_html(os_, *name_);
|
||||
if (opt_show_acc_)
|
||||
os_ << "<br/>";
|
||||
}
|
||||
if (opt_show_acc_)
|
||||
aut_->get_acceptance().to_html
|
||||
(os_, [this](std::ostream& os, int v)
|
||||
{
|
||||
this->output_html_set_aux(os, v);
|
||||
});
|
||||
os_ << ">\n";
|
||||
}
|
||||
os_ << " labelloc=\"t\"\n";
|
||||
}
|
||||
switch (opt_shape_)
|
||||
{
|
||||
case ShapeCircle:
|
||||
os_ << " node [shape=\"circle\"]\n";
|
||||
break;
|
||||
case ShapeEllipse:
|
||||
// Do not print anything. Ellipse is
|
||||
// the default shape used by GraphViz.
|
||||
break;
|
||||
case ShapeAuto:
|
||||
SPOT_UNREACHABLE();
|
||||
}
|
||||
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';
|
||||
os_ << " I [label=\"\", style=invis, ";
|
||||
os_ << (opt_horizontal_ ? "width" : "height");
|
||||
os_ << "=0]\n I -> " << aut_->get_init_state_number() << '\n';
|
||||
}
|
||||
|
||||
void
|
||||
end()
|
||||
{
|
||||
os_ << '}' << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
process_state(unsigned s)
|
||||
{
|
||||
if (mark_states_ &&
|
||||
((opt_bullet && !opt_bullet_but_buchi)
|
||||
|| aut_->num_sets() != 1))
|
||||
{
|
||||
acc_cond::mark_t acc = 0U;
|
||||
for (auto& t: aut_->out(s))
|
||||
{
|
||||
acc = t.acc;
|
||||
break;
|
||||
}
|
||||
|
||||
bool has_name = sn_ && s < sn_->size() && !(*sn_)[s].empty();
|
||||
|
||||
os_ << " " << s << " [label=";
|
||||
if (!opt_html_labels_)
|
||||
{
|
||||
os_ << '"';
|
||||
if (has_name)
|
||||
escape_str(os_, (*sn_)[s]);
|
||||
else if (sprod_)
|
||||
os_ << (*sprod_)[s].first << ',' << (*sprod_)[s].second;
|
||||
else
|
||||
os_ << s;
|
||||
if (acc)
|
||||
{
|
||||
os_ << "\\n";
|
||||
output_set(acc);
|
||||
}
|
||||
os_ << '"';
|
||||
}
|
||||
else
|
||||
{
|
||||
os_ << '<';
|
||||
if (has_name)
|
||||
escape_html(os_, (*sn_)[s]);
|
||||
else if (sprod_)
|
||||
os_ << (*sprod_)[s].first << ',' << (*sprod_)[s].second;
|
||||
else
|
||||
os_ << s;
|
||||
if (acc)
|
||||
{
|
||||
os_ << "<br/>";
|
||||
output_html_set(acc);
|
||||
}
|
||||
os_ << '>';
|
||||
}
|
||||
os_ << "]\n";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
os_ << " " << s << " [label=\"";
|
||||
if (sn_ && s < sn_->size() && !(*sn_)[s].empty())
|
||||
escape_str(os_, (*sn_)[s]);
|
||||
else if (sprod_)
|
||||
os_ << (*sprod_)[s].first << ',' << (*sprod_)[s].second;
|
||||
else
|
||||
os_ << s;
|
||||
os_ << '"';
|
||||
// Use state_acc_sets(), not state_is_accepting() because
|
||||
// on co-Büchi automata we want to mark the rejecting
|
||||
// states.
|
||||
if (mark_states_ && aut_->state_acc_sets(s))
|
||||
os_ << ", peripheries=2";
|
||||
os_ << "]\n";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
process_link(const twa_graph::edge_storage_t& t, int number)
|
||||
{
|
||||
std::string label = bdd_format_formula(aut_->get_dict(), t.cond);
|
||||
os_ << " " << t.src << " -> " << t.dst;
|
||||
if (!opt_html_labels_)
|
||||
{
|
||||
os_ << " [label=\"";
|
||||
escape_str(os_, label);
|
||||
if (!mark_states_)
|
||||
if (auto a = t.acc)
|
||||
{
|
||||
os_ << "\\n";
|
||||
output_set(a);
|
||||
}
|
||||
os_ << '"';
|
||||
}
|
||||
else
|
||||
{
|
||||
os_ << " [label=<";
|
||||
escape_html(os_, label);
|
||||
if (!mark_states_)
|
||||
if (auto a = t.acc)
|
||||
{
|
||||
os_ << "<br/>";
|
||||
output_html_set(a);
|
||||
}
|
||||
os_ << '>';
|
||||
}
|
||||
if (opt_numbered_trans)
|
||||
os_ << ",taillabel=\"" << number << '"';
|
||||
os_ << "]\n";
|
||||
}
|
||||
|
||||
void print(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
aut_ = aut;
|
||||
if (opt_want_state_names_)
|
||||
{
|
||||
sn_ = aut->get_named_prop<std::vector<std::string>>("state-names");
|
||||
// We have no names. Do we have product sources?
|
||||
if (!sn_)
|
||||
{
|
||||
sprod_ = aut->get_named_prop
|
||||
<std::vector<std::pair<unsigned, unsigned>>>
|
||||
("product-states");
|
||||
if (sprod_ && aut->num_states() != sprod_->size())
|
||||
sprod_ = nullptr;
|
||||
}
|
||||
}
|
||||
if (opt_name_)
|
||||
name_ = aut_->get_named_prop<std::string>("automaton-name");
|
||||
mark_states_ = !opt_force_acc_trans_ && aut_->prop_state_acc();
|
||||
if (opt_shape_ == ShapeAuto)
|
||||
{
|
||||
if (sn_ || sprod_ || aut->num_states() > 100)
|
||||
opt_shape_ = ShapeEllipse;
|
||||
else
|
||||
opt_shape_ = ShapeCircle;
|
||||
}
|
||||
auto si =
|
||||
std::unique_ptr<scc_info>(opt_scc_ ? new scc_info(aut) : nullptr);
|
||||
|
||||
start();
|
||||
if (si)
|
||||
{
|
||||
si->determine_unknown_acceptance();
|
||||
|
||||
unsigned sccs = si->scc_count();
|
||||
for (unsigned i = 0; i < sccs; ++i)
|
||||
{
|
||||
os_ << " subgraph cluster_" << i << " {\n";
|
||||
|
||||
// Color the SCC to indicate whether is it accepting.
|
||||
if (!si->is_useful_scc(i))
|
||||
os_ << " color=grey\n";
|
||||
else if (si->is_trivial(i))
|
||||
os_ << " color=black\n";
|
||||
else if (si->is_accepting_scc(i))
|
||||
os_ << " color=green\n";
|
||||
else if (si->is_rejecting_scc(i))
|
||||
os_ << " color=red\n";
|
||||
else
|
||||
// May only occur if the call to
|
||||
// determine_unknown_acceptance() above is removed.
|
||||
os_ << " color=orange\n";
|
||||
|
||||
if (name_ || opt_show_acc_)
|
||||
{
|
||||
// Reset the label, otherwise the graph label would
|
||||
// be inherited by the cluster.
|
||||
os_ << " label=\"\"\n";
|
||||
}
|
||||
for (auto s: si->states_of(i))
|
||||
process_state(s);
|
||||
os_ << " }\n";
|
||||
}
|
||||
}
|
||||
unsigned ns = aut_->num_states();
|
||||
for (unsigned n = 0; n < ns; ++n)
|
||||
{
|
||||
if (!si || !si->reachable_state(n))
|
||||
process_state(n);
|
||||
int trans_num = 0;
|
||||
for (auto& t: aut_->out(n))
|
||||
process_link(t, trans_num++);
|
||||
}
|
||||
end();
|
||||
}
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
std::ostream&
|
||||
print_dot(std::ostream& os, const const_twa_ptr& g,
|
||||
const char* options)
|
||||
{
|
||||
dotty_output d(os, options);
|
||||
auto aut = std::dynamic_pointer_cast<const twa_graph>(g);
|
||||
if (!aut)
|
||||
aut = copy(g, twa::prop_set::all(), true);
|
||||
d.print(aut);
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2012, 2013, 2014, 2015 Laboratoire de Recherche
|
||||
// et Developpement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
#include <twa/fwd.hh>
|
||||
#include "misc/common.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_io
|
||||
/// \brief Print reachable states in dot format.
|
||||
///
|
||||
/// If \a assume_sba is set, this assumes that the automaton
|
||||
/// is an SBA and use double elipse to mark accepting states.
|
||||
///
|
||||
/// \param options an optional string of letters, each indicating a
|
||||
/// different option. Presently the following options are
|
||||
/// supported: 'v' for vertical output, 'h' for horizontal output,
|
||||
/// 't' force transition-based acceptance, 'N' hide the name of the
|
||||
/// automaton, 'n' shows the name, 'c' uses circle-shaped states,
|
||||
/// 'a' shows the acceptance.
|
||||
SPOT_API std::ostream&
|
||||
print_dot(std::ostream& os,
|
||||
const const_twa_ptr& g,
|
||||
const char* options = nullptr);
|
||||
}
|
||||
|
|
@ -1,854 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 <fstream>
|
||||
#include <sstream>
|
||||
#include "dtbasat.hh"
|
||||
#include "reachiter.hh"
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include "sccinfo.hh"
|
||||
#include "twa/bddprint.hh"
|
||||
#include "stats.hh"
|
||||
#include "misc/satsolver.hh"
|
||||
#include "misc/timer.hh"
|
||||
#include "dot.hh"
|
||||
|
||||
// If you set the SPOT_TMPKEEP environment variable the temporary
|
||||
// file used to communicate with the sat solver will be left in
|
||||
// the current directory.
|
||||
//
|
||||
// Additionally, if the following DEBUG macro is set to 1, the CNF
|
||||
// file will be output with a comment before each clause, and an
|
||||
// additional output file (dtba-sat.dbg) will be created with a list
|
||||
// of all positive variables in the result and their meaning.
|
||||
|
||||
#define DEBUG 0
|
||||
#if DEBUG
|
||||
#define dout out << "c "
|
||||
#define trace std::cerr
|
||||
#else
|
||||
#define dout while (0) std::cout
|
||||
#define trace dout
|
||||
#endif
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static bdd_dict_ptr debug_dict;
|
||||
|
||||
struct transition
|
||||
{
|
||||
unsigned src;
|
||||
bdd cond;
|
||||
unsigned dst;
|
||||
|
||||
transition(unsigned src, bdd cond, unsigned dst)
|
||||
: src(src), cond(cond), dst(dst)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator<(const transition& other) const
|
||||
{
|
||||
if (this->src < other.src)
|
||||
return true;
|
||||
if (this->src > other.src)
|
||||
return false;
|
||||
if (this->dst < other.dst)
|
||||
return true;
|
||||
if (this->dst > other.dst)
|
||||
return false;
|
||||
return this->cond.id() < other.cond.id();
|
||||
}
|
||||
|
||||
bool operator==(const transition& other) const
|
||||
{
|
||||
return (this->src == other.src
|
||||
&& this->dst == other.dst
|
||||
&& this->cond.id() == other.cond.id());
|
||||
}
|
||||
};
|
||||
|
||||
struct src_cond
|
||||
{
|
||||
unsigned src;
|
||||
bdd cond;
|
||||
|
||||
src_cond(unsigned src, bdd cond)
|
||||
: src(src), cond(cond)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator<(const src_cond& other) const
|
||||
{
|
||||
if (this->src < other.src)
|
||||
return true;
|
||||
if (this->src > other.src)
|
||||
return false;
|
||||
return this->cond.id() < other.cond.id();
|
||||
}
|
||||
|
||||
bool operator==(const src_cond& other) const
|
||||
{
|
||||
return (this->src == other.src
|
||||
&& this->cond.id() == other.cond.id());
|
||||
}
|
||||
};
|
||||
|
||||
struct state_pair
|
||||
{
|
||||
unsigned a;
|
||||
unsigned b;
|
||||
|
||||
state_pair(unsigned a, unsigned b)
|
||||
: a(a), b(b)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator<(const state_pair& other) const
|
||||
{
|
||||
if (this->a < other.a)
|
||||
return true;
|
||||
if (this->a > other.a)
|
||||
return false;
|
||||
if (this->b < other.b)
|
||||
return true;
|
||||
if (this->b > other.b)
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct path
|
||||
{
|
||||
int src_cand;
|
||||
int src_ref;
|
||||
int dst_cand;
|
||||
int dst_ref;
|
||||
|
||||
path(int src_cand, int src_ref,
|
||||
int dst_cand, int dst_ref)
|
||||
: src_cand(src_cand), src_ref(src_ref),
|
||||
dst_cand(dst_cand), dst_ref(dst_ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator<(const path& other) const
|
||||
{
|
||||
if (this->src_cand < other.src_cand)
|
||||
return true;
|
||||
if (this->src_cand > other.src_cand)
|
||||
return false;
|
||||
if (this->src_ref < other.src_ref)
|
||||
return true;
|
||||
if (this->src_ref > other.src_ref)
|
||||
return false;
|
||||
if (this->dst_cand < other.dst_cand)
|
||||
return true;
|
||||
if (this->dst_cand > other.dst_cand)
|
||||
return false;
|
||||
if (this->dst_ref < other.dst_ref)
|
||||
return true;
|
||||
if (this->dst_ref > other.dst_ref)
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const state_pair& p)
|
||||
{
|
||||
os << '<' << p.a << ',' << p.b << '>';
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const transition& t)
|
||||
{
|
||||
os << '<' << t.src << ','
|
||||
<< bdd_format_formula(debug_dict, t.cond)
|
||||
<< ',' << t.dst << '>';
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const path& p)
|
||||
{
|
||||
os << '<'
|
||||
<< p.src_cand << ','
|
||||
<< p.src_ref << ','
|
||||
<< p.dst_cand << ','
|
||||
<< p.dst_ref << '>';
|
||||
return os;
|
||||
}
|
||||
|
||||
struct dict
|
||||
{
|
||||
typedef std::map<transition, int> trans_map;
|
||||
trans_map transid;
|
||||
trans_map transacc;
|
||||
typedef std::map<int, transition> rev_map;
|
||||
rev_map revtransid;
|
||||
rev_map revtransacc;
|
||||
|
||||
std::map<state_pair, int> prodid;
|
||||
std::map<path, int> pathid_ref;
|
||||
std::map<path, int> pathid_cand;
|
||||
int nvars = 0;
|
||||
unsigned cand_size;
|
||||
};
|
||||
|
||||
unsigned declare_vars(const const_twa_graph_ptr& aut,
|
||||
dict& d,
|
||||
bdd ap,
|
||||
bool state_based,
|
||||
scc_info& sm)
|
||||
{
|
||||
unsigned ref_size = aut->num_states();
|
||||
|
||||
if (d.cand_size == -1U)
|
||||
for (unsigned i = 0; i < ref_size; ++i)
|
||||
if (sm.reachable_state(i))
|
||||
++d.cand_size; // Note that we start from -1U the
|
||||
// cand_size is one less than the
|
||||
// number of reachable states.
|
||||
|
||||
for (unsigned i = 0; i < ref_size; ++i)
|
||||
{
|
||||
if (!sm.reachable_state(i))
|
||||
continue;
|
||||
|
||||
unsigned i_scc = sm.scc_of(i);
|
||||
bool is_trivial = sm.is_trivial(i_scc);
|
||||
|
||||
for (unsigned j = 0; j < d.cand_size; ++j)
|
||||
{
|
||||
d.prodid[state_pair(j, i)] = ++d.nvars;
|
||||
|
||||
// skip trivial SCCs
|
||||
if (is_trivial)
|
||||
continue;
|
||||
|
||||
for (unsigned k = 0; k < ref_size; ++k)
|
||||
{
|
||||
if (!sm.reachable_state(k))
|
||||
continue;
|
||||
if (sm.scc_of(k) != i_scc)
|
||||
continue;
|
||||
for (unsigned l = 0; l < d.cand_size; ++l)
|
||||
{
|
||||
if (i == k && j == l)
|
||||
continue;
|
||||
path p(j, i, l, k);
|
||||
d.pathid_ref[p] = ++d.nvars;
|
||||
d.pathid_cand[p] = ++d.nvars;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < d.cand_size; ++i)
|
||||
{
|
||||
int transacc = -1;
|
||||
if (state_based)
|
||||
// All outgoing transitions use the same acceptance variable.
|
||||
transacc = ++d.nvars;
|
||||
|
||||
for (unsigned j = 0; j < d.cand_size; ++j)
|
||||
{
|
||||
bdd all = bddtrue;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd one = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= one;
|
||||
|
||||
transition t(i, one, j);
|
||||
d.transid[t] = ++d.nvars;
|
||||
d.revtransid.emplace(d.nvars, t);
|
||||
int ta = d.transacc[t] =
|
||||
state_based ? transacc : ++d.nvars;
|
||||
d.revtransacc.emplace(ta, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ref_size;
|
||||
}
|
||||
|
||||
typedef std::pair<int, int> sat_stats;
|
||||
|
||||
static
|
||||
sat_stats dtba_to_sat(std::ostream& out,
|
||||
const const_twa_graph_ptr& ref,
|
||||
dict& d, bool state_based)
|
||||
{
|
||||
clause_counter nclauses;
|
||||
|
||||
// Compute the AP used in the hard way.
|
||||
bdd ap = bddtrue;
|
||||
for (auto& t: ref->edges())
|
||||
ap &= bdd_support(t.cond);
|
||||
|
||||
// Count the number of atomic propositions
|
||||
int nap = 0;
|
||||
{
|
||||
bdd cur = ap;
|
||||
while (cur != bddtrue)
|
||||
{
|
||||
++nap;
|
||||
cur = bdd_high(cur);
|
||||
}
|
||||
nap = 1 << nap;
|
||||
}
|
||||
|
||||
scc_info sm(ref);
|
||||
|
||||
// Number all the SAT variables we may need.
|
||||
unsigned ref_size = declare_vars(ref, d, ap, state_based, sm);
|
||||
|
||||
// empty automaton is impossible
|
||||
if (d.cand_size == 0)
|
||||
{
|
||||
out << "p cnf 1 2\n-1 0\n1 0\n";
|
||||
return std::make_pair(1, 2);
|
||||
}
|
||||
|
||||
// An empty line for the header
|
||||
out << " \n";
|
||||
|
||||
#if DEBUG
|
||||
debug_dict = ref->get_dict();
|
||||
dout << "ref_size: " << ref_size << '\n';
|
||||
dout << "cand_size: " << d.cand_size << '\n';
|
||||
#endif
|
||||
|
||||
dout << "symmetry-breaking clauses\n";
|
||||
unsigned j = 0;
|
||||
bdd all = bddtrue;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd s = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= s;
|
||||
for (unsigned i = 0; i < d.cand_size - 1; ++i)
|
||||
for (unsigned k = i * nap + j + 2; k < d.cand_size; ++k)
|
||||
{
|
||||
transition t(i, s, k);
|
||||
int ti = d.transid[t];
|
||||
dout << "¬" << t << '\n';
|
||||
out << -ti << " 0\n";
|
||||
++nclauses;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
if (!nclauses.nb_clauses())
|
||||
dout << "(none)\n";
|
||||
|
||||
dout << "(1) the candidate automaton is complete\n";
|
||||
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
|
||||
{
|
||||
bdd all = bddtrue;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd s = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= s;
|
||||
|
||||
#if DEBUG
|
||||
dout;
|
||||
for (unsigned q2 = 0; q2 < d.cand_size; q2++)
|
||||
{
|
||||
transition t(q1, s, q2);
|
||||
out << t << "δ";
|
||||
if (q2 != d.cand_size)
|
||||
out << " ∨ ";
|
||||
}
|
||||
out << '\n';
|
||||
#endif
|
||||
|
||||
for (unsigned q2 = 0; q2 < d.cand_size; q2++)
|
||||
{
|
||||
transition t(q1, s, q2);
|
||||
int ti = d.transid[t];
|
||||
|
||||
out << ti << ' ';
|
||||
}
|
||||
out << "0\n";
|
||||
|
||||
++nclauses;
|
||||
}
|
||||
}
|
||||
|
||||
dout << "(2) the initial state is reachable\n";
|
||||
{
|
||||
unsigned init = ref->get_init_state_number();
|
||||
dout << state_pair(0, init) << '\n';
|
||||
out << d.prodid[state_pair(0, init)] << " 0\n";
|
||||
++nclauses;
|
||||
}
|
||||
|
||||
for (std::map<state_pair, int>::const_iterator pit = d.prodid.begin();
|
||||
pit != d.prodid.end(); ++pit)
|
||||
{
|
||||
unsigned q1 = pit->first.a;
|
||||
unsigned q1p = pit->first.b;
|
||||
|
||||
dout << "(3) augmenting paths based on Cand[" << q1
|
||||
<< "] and Ref[" << q1p << "]\n";
|
||||
for (auto& tr: ref->out(q1p))
|
||||
{
|
||||
unsigned dp = tr.dst;
|
||||
bdd all = tr.cond;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd s = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= s;
|
||||
|
||||
for (unsigned q2 = 0; q2 < d.cand_size; q2++)
|
||||
{
|
||||
transition t(q1, s, q2);
|
||||
int ti = d.transid[t];
|
||||
|
||||
state_pair p2(q2, dp);
|
||||
int succ = d.prodid[p2];
|
||||
|
||||
if (pit->second == succ)
|
||||
continue;
|
||||
|
||||
dout << pit->first << " ∧ " << t << "δ → " << p2 << '\n';
|
||||
out << -pit->second << ' ' << -ti << ' '
|
||||
<< succ << " 0\n";
|
||||
++nclauses;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const acc_cond& ra = ref->acc();
|
||||
|
||||
// construction of contraints (4,5) : all loops in the product
|
||||
// where no accepting run is detected in the ref. automaton,
|
||||
// must also be marked as not accepting in the cand. automaton
|
||||
for (unsigned q1p = 0; q1p < ref_size; ++q1p)
|
||||
{
|
||||
if (!sm.reachable_state(q1p))
|
||||
continue;
|
||||
unsigned q1p_scc = sm.scc_of(q1p);
|
||||
if (sm.is_trivial(q1p_scc))
|
||||
continue;
|
||||
for (unsigned q2p = 0; q2p < ref_size; ++q2p)
|
||||
{
|
||||
if (!sm.reachable_state(q2p))
|
||||
continue;
|
||||
// We are only interested in transition that can form a
|
||||
// cycle, so they must belong to the same SCC.
|
||||
if (sm.scc_of(q2p) != q1p_scc)
|
||||
continue;
|
||||
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
|
||||
for (unsigned q2 = 0; q2 < d.cand_size; ++q2)
|
||||
{
|
||||
path p1(q1, q1p, q2, q2p);
|
||||
|
||||
dout << "(4&5) matching paths from reference based on "
|
||||
<< p1 << '\n';
|
||||
|
||||
int pid1;
|
||||
if (q1 == q2 && q1p == q2p)
|
||||
pid1 = d.prodid[state_pair(q1, q1p)];
|
||||
else
|
||||
pid1 = d.pathid_ref[p1];
|
||||
|
||||
for (auto& tr: ref->out(q2p))
|
||||
{
|
||||
unsigned dp = tr.dst;
|
||||
// Skip destinations not in the SCC.
|
||||
if (sm.scc_of(dp) != q1p_scc)
|
||||
continue;
|
||||
|
||||
if (ra.accepting(tr.acc))
|
||||
continue;
|
||||
for (unsigned q3 = 0; q3 < d.cand_size; ++q3)
|
||||
{
|
||||
if (dp == q1p && q3 == q1) // (4) looping
|
||||
{
|
||||
bdd all = tr.cond;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd s = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= s;
|
||||
|
||||
transition t(q2, s, q1);
|
||||
int ti = d.transid[t];
|
||||
int ta = d.transacc[t];
|
||||
|
||||
dout << p1 << "R ∧ " << t << "δ → ¬" << t
|
||||
<< "F\n";
|
||||
out << -pid1 << ' ' << -ti << ' '
|
||||
<< -ta << " 0\n";
|
||||
++nclauses;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else // (5) not looping
|
||||
{
|
||||
path p2 = path(q1, q1p, q3, dp);
|
||||
int pid2 = d.pathid_ref[p2];
|
||||
|
||||
if (pid1 == pid2)
|
||||
continue;
|
||||
|
||||
bdd all = tr.cond;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd s = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= s;
|
||||
|
||||
transition t(q2, s, q3);
|
||||
int ti = d.transid[t];
|
||||
|
||||
dout << p1 << "R ∧ " << t << "δ → " << p2
|
||||
<< "R\n";
|
||||
out << -pid1 << ' ' << -ti << ' '
|
||||
<< pid2 << " 0\n";
|
||||
++nclauses;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// construction of contraints (6,7): all loops in the product
|
||||
// where accepting run is detected in the ref. automaton, must
|
||||
// also be marked as accepting in the candidate.
|
||||
for (unsigned q1p = 0; q1p < ref_size; ++q1p)
|
||||
{
|
||||
if (!sm.reachable_state(q1p))
|
||||
continue;
|
||||
unsigned q1p_scc = sm.scc_of(q1p);
|
||||
if (sm.is_trivial(q1p_scc))
|
||||
continue;
|
||||
for (unsigned q2p = 0; q2p < ref_size; ++q2p)
|
||||
{
|
||||
if (!sm.reachable_state(q2p))
|
||||
continue;
|
||||
// We are only interested in transition that can form a
|
||||
// cycle, so they must belong to the same SCC.
|
||||
if (sm.scc_of(q2p) != q1p_scc)
|
||||
continue;
|
||||
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
|
||||
for (unsigned q2 = 0; q2 < d.cand_size; ++q2)
|
||||
{
|
||||
path p1(q1, q1p, q2, q2p);
|
||||
dout << "(6&7) matching paths from candidate based on "
|
||||
<< p1 << '\n';
|
||||
|
||||
int pid1;
|
||||
if (q1 == q2 && q1p == q2p)
|
||||
pid1 = d.prodid[state_pair(q1, q1p)];
|
||||
else
|
||||
pid1 = d.pathid_cand[p1];
|
||||
|
||||
for (auto& tr: ref->out(q2p))
|
||||
{
|
||||
unsigned dp = tr.dst;
|
||||
// Skip destinations not in the SCC.
|
||||
if (sm.scc_of(dp) != q1p_scc)
|
||||
continue;
|
||||
for (unsigned q3 = 0; q3 < d.cand_size; q3++)
|
||||
{
|
||||
if (dp == q1p && q3 == q1) // (6) looping
|
||||
{
|
||||
// We only care about the looping case if
|
||||
// it is accepting in the reference.
|
||||
if (!ra.accepting(tr.acc))
|
||||
continue;
|
||||
bdd all = tr.cond;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd s = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= s;
|
||||
|
||||
transition t(q2, s, q1);
|
||||
int ti = d.transid[t];
|
||||
int ta = d.transacc[t];
|
||||
|
||||
dout << p1 << "C ∧ " << t << "δ → " << t
|
||||
<< "F\n";
|
||||
out << -pid1 << ' ' << -ti << ' ' << ta
|
||||
<< " 0\n";
|
||||
++nclauses;
|
||||
}
|
||||
}
|
||||
else // (7) no loop
|
||||
{
|
||||
path p2 = path(q1, q1p, q3, dp);
|
||||
int pid2 = d.pathid_cand[p2];
|
||||
|
||||
if (pid1 == pid2)
|
||||
continue;
|
||||
|
||||
bdd all = tr.cond;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd s = bdd_satoneset(all, ap, bddfalse);
|
||||
all -= s;
|
||||
|
||||
transition t(q2, s, q3);
|
||||
int ti = d.transid[t];
|
||||
int ta = d.transacc[t];
|
||||
|
||||
dout << p1 << "C ∧ " << t << "δ ∧ ¬"
|
||||
<< t << "F → " << p2 << "C\n";
|
||||
|
||||
out << -pid1 << ' ' << -ti << ' '
|
||||
<< ta << ' ' << pid2 << " 0\n";
|
||||
++nclauses;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.seekp(0);
|
||||
out << "p cnf " << d.nvars << ' ' << nclauses.nb_clauses();
|
||||
return std::make_pair(d.nvars, nclauses.nb_clauses());
|
||||
}
|
||||
|
||||
static twa_graph_ptr
|
||||
sat_build(const satsolver::solution& solution, dict& satdict,
|
||||
const_twa_graph_ptr aut, bool state_based)
|
||||
{
|
||||
auto autdict = aut->get_dict();
|
||||
auto a = make_twa_graph(autdict);
|
||||
a->copy_ap_of(aut);
|
||||
acc_cond::mark_t acc = a->set_buchi();
|
||||
if (state_based)
|
||||
a->prop_state_acc(true);
|
||||
a->prop_deterministic(true);
|
||||
a->new_states(satdict.cand_size);
|
||||
|
||||
unsigned last_aut_trans = -1U;
|
||||
const transition* last_sat_trans = nullptr;
|
||||
|
||||
#if DEBUG
|
||||
std::fstream out("dtba-sat.dbg",
|
||||
std::ios_base::trunc | std::ios_base::out);
|
||||
out.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
std::set<int> positive;
|
||||
#endif
|
||||
|
||||
dout << "--- transition variables ---\n";
|
||||
std::set<int> acc_states;
|
||||
std::set<src_cond> seen_trans;
|
||||
for (int v: solution)
|
||||
{
|
||||
if (v < 0) // FIXME: maybe we can have (v < NNN)?
|
||||
continue;
|
||||
|
||||
#if DEBUG
|
||||
positive.insert(v);
|
||||
#endif
|
||||
|
||||
dict::rev_map::const_iterator t = satdict.revtransid.find(v);
|
||||
|
||||
if (t != satdict.revtransid.end())
|
||||
{
|
||||
// Skip (s,l,d2) if we have already seen some (s,l,d1).
|
||||
if (seen_trans.insert(src_cond(t->second.src,
|
||||
t->second.cond)).second)
|
||||
{
|
||||
// Mark the transition as accepting if the source is.
|
||||
bool accept = state_based
|
||||
&& acc_states.find(t->second.src) != acc_states.end();
|
||||
|
||||
last_aut_trans =
|
||||
a->new_acc_edge(t->second.src, t->second.dst,
|
||||
t->second.cond, accept);
|
||||
last_sat_trans = &t->second;
|
||||
|
||||
dout << v << '\t' << t->second << "δ\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
t = satdict.revtransacc.find(v);
|
||||
if (t != satdict.revtransacc.end())
|
||||
{
|
||||
dout << v << '\t' << t->second << "F\n";
|
||||
if (last_sat_trans && t->second == *last_sat_trans)
|
||||
{
|
||||
assert(!state_based);
|
||||
// This assumes that the SAT solvers output
|
||||
// variables in increasing order.
|
||||
a->edge_data(last_aut_trans).acc = acc;
|
||||
}
|
||||
else if (state_based)
|
||||
{
|
||||
// Accepting translations actually correspond to
|
||||
// states and are announced before listing
|
||||
// outgoing transitions. Again, this assumes
|
||||
// that the SAT solvers output variables in
|
||||
// increasing order.
|
||||
acc_states.insert(t->second.src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
dout << "--- state_pair variables ---\n";
|
||||
for (auto pit: satdict.prodid)
|
||||
if (positive.find(pit.second) != positive.end())
|
||||
dout << pit.second << '\t' << pit.first << "C\n";
|
||||
else
|
||||
dout << -pit.second << "\t¬" << pit.first << "C\n";
|
||||
|
||||
dout << "--- pathid_cand variables ---\n";
|
||||
for (auto pit: satdict.pathid_cand)
|
||||
if (positive.find(pit.second) != positive.end())
|
||||
dout << pit.second << '\t' << pit.first << "C\n";
|
||||
else
|
||||
dout << -pit.second << "\t¬" << pit.first << "C\n";
|
||||
|
||||
dout << "--- pathid_ref variables ---\n";
|
||||
for (auto pit: satdict.pathid_ref)
|
||||
if (positive.find(pit.second) != positive.end())
|
||||
dout << pit.second << '\t' << pit.first << "R\n";
|
||||
else
|
||||
dout << -pit.second << "\t¬" << pit.first << "C\n";
|
||||
#endif
|
||||
a->merge_edges();
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
dtba_sat_synthetize(const const_twa_graph_ptr& a,
|
||||
int target_state_number, bool state_based)
|
||||
{
|
||||
if (!a->acc().is_buchi())
|
||||
throw std::runtime_error
|
||||
("dtba_sat() can only work with Büchi acceptance");
|
||||
if (target_state_number == 0)
|
||||
return nullptr;
|
||||
trace << "dtba_sat_synthetize(..., states = " << target_state_number
|
||||
<< ", state_based = " << state_based << ")\n";
|
||||
dict d;
|
||||
d.cand_size = target_state_number;
|
||||
|
||||
satsolver solver;
|
||||
satsolver::solution_pair solution;
|
||||
|
||||
timer_map t;
|
||||
t.start("encode");
|
||||
sat_stats s = dtba_to_sat(solver(), a, d, state_based);
|
||||
t.stop("encode");
|
||||
t.start("solve");
|
||||
solution = solver.get_solution();
|
||||
t.stop("solve");
|
||||
|
||||
twa_graph_ptr res = nullptr;
|
||||
if (!solution.second.empty())
|
||||
res = sat_build(solution.second, d, a, state_based);
|
||||
|
||||
// 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 log = []()
|
||||
{
|
||||
auto s = getenv("SPOT_SATLOG");
|
||||
return s ? s : "";
|
||||
}();
|
||||
if (!log.empty())
|
||||
{
|
||||
std::fstream out(log,
|
||||
std::ios_base::app | std::ios_base::out);
|
||||
out.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
const timer& te = t.timer("encode");
|
||||
const timer& ts = t.timer("solve");
|
||||
out << target_state_number << ',';
|
||||
if (res)
|
||||
{
|
||||
twa_sub_statistics st = sub_stats_reachable(res);
|
||||
out << st.states << ',' << st.edges << ',' << st.transitions;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << ",,";
|
||||
}
|
||||
out << ','
|
||||
<< s.first << ',' << s.second << ','
|
||||
<< te.utime() << ',' << te.stime() << ','
|
||||
<< ts.utime() << ',' << ts.stime() << '\n';
|
||||
}
|
||||
static bool show = getenv("SPOT_SATSHOW");
|
||||
if (show && res)
|
||||
print_dot(std::cout, res);
|
||||
|
||||
trace << "dtba_sat_synthetize(...) = " << res << '\n';
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
dtba_sat_minimize(const const_twa_graph_ptr& a,
|
||||
bool state_based, int max_states)
|
||||
{
|
||||
int n_states = (max_states < 0) ?
|
||||
stats_reachable(a).states : max_states + 1;
|
||||
|
||||
twa_graph_ptr prev = nullptr;
|
||||
for (;;)
|
||||
{
|
||||
auto next =
|
||||
dtba_sat_synthetize(prev ? prev : a, --n_states, state_based);
|
||||
if (!next)
|
||||
return prev;
|
||||
else
|
||||
n_states = stats_reachable(next).states;
|
||||
prev = next;
|
||||
}
|
||||
SPOT_UNREACHABLE();
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
dtba_sat_minimize_dichotomy(const const_twa_graph_ptr& a,
|
||||
bool state_based, int max_states)
|
||||
{
|
||||
if (max_states < 0)
|
||||
max_states = stats_reachable(a).states - 1;
|
||||
int min_states = 1;
|
||||
|
||||
twa_graph_ptr prev = nullptr;
|
||||
while (min_states <= max_states)
|
||||
{
|
||||
int target = (max_states + min_states) / 2;
|
||||
auto next = dtba_sat_synthetize(prev ? prev : a, target, state_based);
|
||||
if (!next)
|
||||
{
|
||||
min_states = target + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = next;
|
||||
max_states = stats_reachable(next).states - 1;
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Attempt to synthetize an equivalent deterministic TBA
|
||||
/// with a SAT solver.
|
||||
///
|
||||
/// \param a the input TGA. It should have only one acceptance
|
||||
/// set and be deterministic. I.e., it should be a deterministic TBA.
|
||||
///
|
||||
/// \param target_state_number the desired number of states wanted
|
||||
/// in the resulting automaton. The result may have less than \a
|
||||
/// target_state_number reachable states.
|
||||
///
|
||||
/// \param state_based set to true to force all outgoing transitions
|
||||
/// of a state to share the same acceptance condition, effectively
|
||||
/// turning the TBA into a BA.
|
||||
///
|
||||
/// If no equivalent deterministic TBA with \a target_state_number
|
||||
/// states is found, a null pointer
|
||||
SPOT_API twa_graph_ptr
|
||||
dtba_sat_synthetize(const const_twa_graph_ptr& a,
|
||||
int target_state_number,
|
||||
bool state_based = false);
|
||||
|
||||
/// \brief Attempt to minimize a deterministic TBA with a SAT solver.
|
||||
///
|
||||
/// This calls dtba_sat_synthetize() in a loop, with a decreasing
|
||||
/// number of states, and returns the last successfully built TBA.
|
||||
///
|
||||
/// If no smaller TBA exist, this returns a null pointer.
|
||||
SPOT_API twa_graph_ptr
|
||||
dtba_sat_minimize(const const_twa_graph_ptr& a,
|
||||
bool state_based = false,
|
||||
int max_states = -1);
|
||||
|
||||
/// \brief Attempt to minimize a deterministic TBA with a SAT solver.
|
||||
///
|
||||
/// This calls dtba_sat_synthetize() in a loop, but attempting to
|
||||
/// find the minimum number of states using a binary search.
|
||||
//
|
||||
/// If no smaller TBA exist, this returns a null pointer.
|
||||
SPOT_API twa_graph_ptr
|
||||
dtba_sat_minimize_dichotomy(const const_twa_graph_ptr& a,
|
||||
bool state_based = false,
|
||||
int max_states = -1);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,102 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Attempt to synthetize an equivalent deterministic TωA
|
||||
/// with a SAT solver.
|
||||
///
|
||||
/// \param a the input TωA. It should be a deterministic TωA.
|
||||
///
|
||||
/// \param target_acc_number is the number of acceptance sets wanted
|
||||
/// in the result.
|
||||
///
|
||||
/// \param target_acc the target acceptance condition
|
||||
///
|
||||
/// \param target_state_number is the desired number of states in
|
||||
/// the result. The output may have less than \a
|
||||
/// target_state_number reachable states.
|
||||
///
|
||||
/// \param state_based set to true to force all outgoing transitions
|
||||
/// of a state to share the same acceptance conditions.
|
||||
///
|
||||
/// \param colored if true, force all transitions to belong to
|
||||
/// exactly one acceptance set.
|
||||
///
|
||||
/// This functions attempts to find a TωA with \a target_acc_number
|
||||
/// acceptance sets and target_state_number states that is
|
||||
/// equivalent to \a a. If no such TωA is found, a null pointer is
|
||||
/// returned.
|
||||
SPOT_API twa_graph_ptr
|
||||
dtwa_sat_synthetize(const const_twa_graph_ptr& a,
|
||||
unsigned target_acc_number,
|
||||
const acc_cond::acc_code& target_acc,
|
||||
int target_state_number,
|
||||
bool state_based = false,
|
||||
bool colored = false);
|
||||
|
||||
/// \brief Attempt to minimize a deterministic TωA with a SAT solver.
|
||||
///
|
||||
/// This calls dtwa_sat_synthetize() in a loop, with a decreasing
|
||||
/// number of states, and returns the last successfully built TGBA.
|
||||
///
|
||||
/// If no smaller TGBA exists, this returns a null pointer.
|
||||
SPOT_API twa_graph_ptr
|
||||
dtwa_sat_minimize(const const_twa_graph_ptr& a,
|
||||
unsigned target_acc_number,
|
||||
const acc_cond::acc_code& target_acc,
|
||||
bool state_based = false,
|
||||
int max_states = -1,
|
||||
bool colored = false);
|
||||
|
||||
/// \brief Attempt to minimize a deterministic TωA with a SAT solver.
|
||||
///
|
||||
/// This calls dtwa_sat_synthetize() in a loop, but attempting to
|
||||
/// find the minimum number of states using a binary search.
|
||||
//
|
||||
/// If no smaller TBA exist, this returns a null pointer.
|
||||
SPOT_API twa_graph_ptr
|
||||
dtwa_sat_minimize_dichotomy(const const_twa_graph_ptr& a,
|
||||
unsigned target_acc_number,
|
||||
const acc_cond::acc_code& target_acc,
|
||||
bool state_based = false,
|
||||
int max_states = -1,
|
||||
bool colored = false);
|
||||
|
||||
/// \brief High-level interface to SAT-based minimization
|
||||
///
|
||||
/// Minimize the automaton \a aut, using options \a opt.
|
||||
/// These options are given as a comma-separated list of
|
||||
/// assignments of the form:
|
||||
///
|
||||
/// states = 10 // synthetize automaton with fixed number of states
|
||||
/// max-states = 20 // minimize starting from this upper bound
|
||||
/// acc = "generalized-Buchi 2"
|
||||
/// acc = "Rabin 3"
|
||||
/// acc = "same" /* default */
|
||||
/// dichotomy = 1 // use dichotomy instead of decreasing loop
|
||||
/// colored = 1 // build a colored TωA
|
||||
///
|
||||
SPOT_API twa_graph_ptr
|
||||
sat_minimize(twa_graph_ptr aut, const char* opt, bool state_based = false);
|
||||
}
|
||||
|
|
@ -1,756 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2011, 2012, 2013, 2014, 2015 Laboratoire de
|
||||
// Recherche et Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 <sstream>
|
||||
#include "emptiness.hh"
|
||||
#include "bfssteps.hh"
|
||||
#include "gtec/gtec.hh"
|
||||
#include "gv04.hh"
|
||||
#include "magic.hh"
|
||||
#include "misc/hash.hh"
|
||||
#include "se05.hh"
|
||||
#include "tau03.hh"
|
||||
#include "tau03opt.hh"
|
||||
#include "twa/bddprint.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
// emptiness_check_result
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
twa_run_ptr
|
||||
emptiness_check_result::accepting_run()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const unsigned_statistics*
|
||||
emptiness_check_result::statistics() const
|
||||
{
|
||||
return dynamic_cast<const unsigned_statistics*>(this);
|
||||
}
|
||||
|
||||
const char*
|
||||
emptiness_check_result::parse_options(char* options)
|
||||
{
|
||||
option_map old(o_);
|
||||
const char* s = o_.parse_options(options);
|
||||
options_updated(old);
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
emptiness_check_result::options_updated(const option_map&)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// emptiness_check
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
emptiness_check::~emptiness_check()
|
||||
{
|
||||
}
|
||||
|
||||
const unsigned_statistics*
|
||||
emptiness_check::statistics() const
|
||||
{
|
||||
return dynamic_cast<const unsigned_statistics*>(this);
|
||||
}
|
||||
|
||||
const ec_statistics*
|
||||
emptiness_check::emptiness_check_statistics() const
|
||||
{
|
||||
return dynamic_cast<const ec_statistics*>(this);
|
||||
}
|
||||
|
||||
const char*
|
||||
emptiness_check::parse_options(char* options)
|
||||
{
|
||||
option_map old(o_);
|
||||
const char* s = o_.parse_options(options);
|
||||
options_updated(old);
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
emptiness_check::options_updated(const option_map&)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
emptiness_check::safe() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
emptiness_check::print_stats(std::ostream& os) const
|
||||
{
|
||||
return os;
|
||||
}
|
||||
|
||||
// emptiness_check_instantiator
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace
|
||||
{
|
||||
struct ec_algo
|
||||
{
|
||||
const char* name;
|
||||
emptiness_check_ptr(*construct)(const const_twa_ptr&,
|
||||
spot::option_map);
|
||||
unsigned int min_acc;
|
||||
unsigned int max_acc;
|
||||
};
|
||||
|
||||
ec_algo ec_algos[] =
|
||||
{
|
||||
{ "Cou99", couvreur99, 0, -1U },
|
||||
{ "CVWY90", magic_search, 0, 1 },
|
||||
{ "GV04", explicit_gv04_check, 0, 1 },
|
||||
{ "SE05", se05, 0, 1 },
|
||||
{ "Tau03", explicit_tau03_search, 1, -1U },
|
||||
{ "Tau03_opt", explicit_tau03_opt_search, 0, -1U },
|
||||
};
|
||||
}
|
||||
|
||||
emptiness_check_instantiator::emptiness_check_instantiator(option_map o,
|
||||
void* i)
|
||||
: o_(o), info_(i)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned int
|
||||
emptiness_check_instantiator::min_acceptance_conditions() const
|
||||
{
|
||||
return static_cast<ec_algo*>(info_)->min_acc;
|
||||
}
|
||||
|
||||
unsigned int
|
||||
emptiness_check_instantiator::max_acceptance_conditions() const
|
||||
{
|
||||
return static_cast<ec_algo*>(info_)->max_acc;
|
||||
}
|
||||
|
||||
emptiness_check_ptr
|
||||
emptiness_check_instantiator::instantiate(const const_twa_ptr& a) const
|
||||
{
|
||||
return static_cast<ec_algo*>(info_)->construct(a, o_);
|
||||
}
|
||||
|
||||
emptiness_check_instantiator_ptr
|
||||
make_emptiness_check_instantiator(const char* name, const char** err)
|
||||
{
|
||||
// Skip spaces.
|
||||
while (*name && strchr(" \t\n", *name))
|
||||
++name;
|
||||
|
||||
const char* opt_str = strchr(name, '(');
|
||||
option_map o;
|
||||
if (opt_str)
|
||||
{
|
||||
const char* opt_start = opt_str + 1;
|
||||
const char* opt_end = strchr(opt_start, ')');
|
||||
if (!opt_end)
|
||||
{
|
||||
*err = opt_start;
|
||||
return nullptr;
|
||||
}
|
||||
std::string opt(opt_start, opt_end);
|
||||
|
||||
const char* res = o.parse_options(opt.c_str());
|
||||
if (res)
|
||||
{
|
||||
*err = opt.c_str() - res + opt_start;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!opt_str)
|
||||
opt_str = name + strlen(name);
|
||||
|
||||
// Ignore spaces before `(' (or trailing spaces).
|
||||
while (opt_str > name && strchr(" \t\n", *--opt_str))
|
||||
continue;
|
||||
std::string n(name, opt_str + 1);
|
||||
|
||||
|
||||
ec_algo* info = ec_algos;
|
||||
for (unsigned i = 0; i < sizeof(ec_algos)/sizeof(*ec_algos); ++i, ++info)
|
||||
if (n == info->name)
|
||||
{
|
||||
*err = nullptr;
|
||||
|
||||
struct emptiness_check_instantiator_aux:
|
||||
public emptiness_check_instantiator
|
||||
{
|
||||
emptiness_check_instantiator_aux(option_map o, void* i):
|
||||
emptiness_check_instantiator(o, i)
|
||||
{
|
||||
}
|
||||
};
|
||||
return std::make_shared<emptiness_check_instantiator_aux>(o, info);
|
||||
}
|
||||
*err = name;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// twa_run
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
twa_run::~twa_run()
|
||||
{
|
||||
for (auto i : prefix)
|
||||
i.s->destroy();
|
||||
for (auto i : cycle)
|
||||
i.s->destroy();
|
||||
}
|
||||
|
||||
twa_run::twa_run(const twa_run& run)
|
||||
{
|
||||
aut = run.aut;
|
||||
for (step s : run.prefix)
|
||||
{
|
||||
s.s = s.s->clone();
|
||||
prefix.push_back(s);
|
||||
}
|
||||
for (step s : run.cycle)
|
||||
{
|
||||
s.s = s.s->clone();
|
||||
cycle.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
twa_run&
|
||||
twa_run::operator=(const twa_run& run)
|
||||
{
|
||||
if (&run != this)
|
||||
{
|
||||
this->~twa_run();
|
||||
new(this) twa_run(run);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const twa_run& run)
|
||||
{
|
||||
auto& a = run.aut;
|
||||
bdd_dict_ptr d = a->get_dict();
|
||||
|
||||
auto pstep = [&](const twa_run::step& st)
|
||||
{
|
||||
os << " " << a->format_state(st.s) << "\n | ";
|
||||
bdd_print_formula(os, d, st.label);
|
||||
if (st.acc)
|
||||
os << '\t' << st.acc;
|
||||
os << '\n';
|
||||
};
|
||||
|
||||
os << "Prefix:\n";
|
||||
for (auto& s: run.prefix)
|
||||
pstep(s);
|
||||
os << "Cycle:\n";
|
||||
for (auto& s: run.cycle)
|
||||
pstep(s);
|
||||
return os;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class shortest_path: public bfs_steps
|
||||
{
|
||||
public:
|
||||
shortest_path(const const_twa_ptr& a)
|
||||
: bfs_steps(a), target(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
~shortest_path()
|
||||
{
|
||||
state_set::const_iterator i = seen.begin();
|
||||
while (i != seen.end())
|
||||
{
|
||||
const state* ptr = *i;
|
||||
++i;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
set_target(const state_set* t)
|
||||
{
|
||||
target = t;
|
||||
}
|
||||
|
||||
const state*
|
||||
search(const state* start, twa_run::steps& l)
|
||||
{
|
||||
return this->bfs_steps::search(filter(start), l);
|
||||
}
|
||||
|
||||
const state*
|
||||
filter(const state* s)
|
||||
{
|
||||
state_set::const_iterator i = seen.find(s);
|
||||
if (i == seen.end())
|
||||
seen.insert(s);
|
||||
else
|
||||
{
|
||||
s->destroy();
|
||||
s = *i;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool
|
||||
match(twa_run::step&, const state* dest)
|
||||
{
|
||||
return target->find(dest) != target->end();
|
||||
}
|
||||
|
||||
private:
|
||||
state_set seen;
|
||||
const state_set* target;
|
||||
};
|
||||
}
|
||||
|
||||
twa_run_ptr twa_run::reduce() const
|
||||
{
|
||||
auto& a = aut;
|
||||
auto res = std::make_shared<twa_run>(a);
|
||||
state_set ss;
|
||||
shortest_path shpath(a);
|
||||
shpath.set_target(&ss);
|
||||
|
||||
// We want to find a short segment of the original cycle that
|
||||
// contains all acceptance conditions.
|
||||
|
||||
const state* segment_start; // The initial state of the segment.
|
||||
const state* segment_next; // The state immediately after the segment.
|
||||
|
||||
// Start from the end of the original cycle, and rewind until all
|
||||
// acceptance sets have been seen.
|
||||
acc_cond::mark_t seen_acc = 0U;
|
||||
twa_run::steps::const_iterator seg = cycle.end();
|
||||
do
|
||||
{
|
||||
assert(seg != cycle.begin());
|
||||
--seg;
|
||||
seen_acc |= seg->acc;
|
||||
}
|
||||
while (!a->acc().accepting(seen_acc));
|
||||
segment_start = seg->s;
|
||||
|
||||
// Now go forward and ends the segment as soon as we have seen all
|
||||
// acceptance sets, cloning it in our result along the way.
|
||||
seen_acc = 0U;
|
||||
do
|
||||
{
|
||||
assert(seg != cycle.end());
|
||||
seen_acc |= seg->acc;
|
||||
|
||||
twa_run::step st = { seg->s->clone(), seg->label, seg->acc };
|
||||
res->cycle.push_back(st);
|
||||
|
||||
++seg;
|
||||
}
|
||||
while (!a->acc().accepting(seen_acc));
|
||||
segment_next = seg == cycle.end() ? cycle.front().s : seg->s;
|
||||
|
||||
// Close this cycle if needed, that is, compute a cycle going
|
||||
// from the state following the segment to its starting state.
|
||||
if (segment_start != segment_next)
|
||||
{
|
||||
ss.insert(segment_start);
|
||||
const state* s = shpath.search(segment_next->clone(), res->cycle);
|
||||
ss.clear();
|
||||
assert(s->compare(segment_start) == 0);
|
||||
(void)s;
|
||||
}
|
||||
|
||||
// Compute the prefix: it's the shortest path from the initial
|
||||
// state of the automata to any state of the cycle.
|
||||
|
||||
// Register all states from the cycle as target of the BFS.
|
||||
for (twa_run::steps::const_iterator i = res->cycle.begin();
|
||||
i != res->cycle.end(); ++i)
|
||||
ss.insert(i->s);
|
||||
|
||||
const state* prefix_start = a->get_init_state();
|
||||
// There are two cases: either the initial state is already on
|
||||
// the cycle, or it is not. If it is, we will have to rotate
|
||||
// the cycle so it begins on this position. Otherwise we will shift
|
||||
// the cycle so it begins on the state that follows the prefix.
|
||||
// cycle_entry_point is that state.
|
||||
const state* cycle_entry_point;
|
||||
state_set::const_iterator ps = ss.find(prefix_start);
|
||||
if (ps != ss.end())
|
||||
{
|
||||
// The initial state is on the cycle.
|
||||
prefix_start->destroy();
|
||||
cycle_entry_point = *ps;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This initial state is outside the cycle. Compute the prefix.
|
||||
cycle_entry_point = shpath.search(prefix_start, res->prefix);
|
||||
}
|
||||
|
||||
// Locate cycle_entry_point on the cycle.
|
||||
twa_run::steps::iterator cycle_ep_it;
|
||||
for (cycle_ep_it = res->cycle.begin();
|
||||
cycle_ep_it != res->cycle.end()
|
||||
&& cycle_entry_point->compare(cycle_ep_it->s); ++cycle_ep_it)
|
||||
continue;
|
||||
assert(cycle_ep_it != res->cycle.end());
|
||||
|
||||
// Now shift the cycle so it starts on cycle_entry_point.
|
||||
res->cycle.splice(res->cycle.end(), res->cycle,
|
||||
res->cycle.begin(), cycle_ep_it);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
static void
|
||||
print_annotation(std::ostream& os,
|
||||
const const_twa_ptr& a,
|
||||
const twa_succ_iterator* i)
|
||||
{
|
||||
std::string s = a->transition_annotation(i);
|
||||
if (s == "")
|
||||
return;
|
||||
os << ' ' << s;
|
||||
}
|
||||
}
|
||||
|
||||
bool twa_run::replay(std::ostream& os, bool debug) const
|
||||
{
|
||||
const state* s = aut->get_init_state();
|
||||
int serial = 1;
|
||||
const twa_run::steps* l;
|
||||
std::string in;
|
||||
acc_cond::mark_t all_acc = 0U;
|
||||
bool all_acc_seen = false;
|
||||
typedef std::unordered_map<const state*, std::set<int>,
|
||||
state_ptr_hash, state_ptr_equal> state_map;
|
||||
state_map seen;
|
||||
|
||||
if (prefix.empty())
|
||||
{
|
||||
l = &cycle;
|
||||
in = "cycle";
|
||||
if (!debug)
|
||||
os << "No prefix.\nCycle:\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
l = &prefix;
|
||||
in = "prefix";
|
||||
if (!debug)
|
||||
os << "Prefix:\n";
|
||||
}
|
||||
|
||||
twa_run::steps::const_iterator i = l->begin();
|
||||
|
||||
if (s->compare(i->s))
|
||||
{
|
||||
if (debug)
|
||||
os << "ERROR: First state of run (in " << in << "): "
|
||||
<< aut->format_state(i->s)
|
||||
<< "\ndoes not match initial state of automata: "
|
||||
<< aut->format_state(s) << '\n';
|
||||
s->destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (; i != l->end(); ++serial)
|
||||
{
|
||||
if (debug)
|
||||
{
|
||||
// Keep track of the serial associated to each state so we
|
||||
// can note duplicate states and make the replay easier to read.
|
||||
state_map::iterator o = seen.find(s);
|
||||
std::ostringstream msg;
|
||||
if (o != seen.end())
|
||||
{
|
||||
for (auto d: o->second)
|
||||
msg << " == " << d;
|
||||
o->second.insert(serial);
|
||||
s->destroy();
|
||||
s = o->first;
|
||||
}
|
||||
else
|
||||
{
|
||||
seen[s].insert(serial);
|
||||
}
|
||||
os << "state " << serial << " in " << in << msg.str() << ": ";
|
||||
}
|
||||
else
|
||||
{
|
||||
os << " ";
|
||||
}
|
||||
os << aut->format_state(s) << '\n';
|
||||
|
||||
// expected outgoing transition
|
||||
bdd label = i->label;
|
||||
acc_cond::mark_t acc = i->acc;
|
||||
|
||||
// compute the next expected state
|
||||
const state* next;
|
||||
++i;
|
||||
if (i != l->end())
|
||||
{
|
||||
next = i->s;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (l == &prefix)
|
||||
{
|
||||
l = &cycle;
|
||||
in = "cycle";
|
||||
i = l->begin();
|
||||
if (!debug)
|
||||
os << "Cycle:\n";
|
||||
}
|
||||
next = l->begin()->s;
|
||||
}
|
||||
|
||||
// browse the actual outgoing transitions
|
||||
twa_succ_iterator* j = aut->succ_iter(s);
|
||||
// When not debugging, S is not used as key in SEEN, so we can
|
||||
// destroy it right now.
|
||||
if (!debug)
|
||||
s->destroy();
|
||||
if (j->first())
|
||||
do
|
||||
{
|
||||
if (j->cond() != label
|
||||
|| j->acc() != acc)
|
||||
continue;
|
||||
|
||||
const state* s2 = j->dst();
|
||||
if (s2->compare(next))
|
||||
{
|
||||
s2->destroy();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
s = s2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (j->next());
|
||||
if (j->done())
|
||||
{
|
||||
if (debug)
|
||||
{
|
||||
os << "ERROR: no transition with label="
|
||||
<< bdd_format_formula(aut->get_dict(), label)
|
||||
<< " and acc=" << aut->acc().format(acc)
|
||||
<< " leaving state " << serial
|
||||
<< " for state " << aut->format_state(next) << '\n'
|
||||
<< "The following transitions leave state " << serial
|
||||
<< ":\n";
|
||||
if (j->first())
|
||||
do
|
||||
{
|
||||
const state* s2 = j->dst();
|
||||
os << " *";
|
||||
print_annotation(os, aut, j);
|
||||
os << " label="
|
||||
<< bdd_format_formula(aut->get_dict(),
|
||||
j->cond())
|
||||
<< " and acc="
|
||||
<< (aut->acc().format
|
||||
(j->acc()))
|
||||
<< " going to " << aut->format_state(s2) << '\n';
|
||||
s2->destroy();
|
||||
}
|
||||
while (j->next());
|
||||
}
|
||||
aut->release_iter(j);
|
||||
s->destroy();
|
||||
return false;
|
||||
}
|
||||
if (debug)
|
||||
{
|
||||
os << "transition";
|
||||
print_annotation(os, aut, j);
|
||||
os << " with label="
|
||||
<< bdd_format_formula(aut->get_dict(), label)
|
||||
<< " and acc=" << aut->acc().format(acc)
|
||||
<< std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
os << " | ";
|
||||
print_annotation(os, aut, j);
|
||||
bdd_print_formula(os, aut->get_dict(), label);
|
||||
os << '\t';
|
||||
aut->acc().format(acc);
|
||||
os << std::endl;
|
||||
}
|
||||
aut->release_iter(j);
|
||||
|
||||
// Sum acceptance conditions.
|
||||
//
|
||||
// (Beware l and i designate the next step to consider.
|
||||
// Therefore if i is at the beginning of the cycle, `acc'
|
||||
// contains the acceptance conditions of the last transition
|
||||
// in the prefix; we should not account it.)
|
||||
if (l == &cycle && i != l->begin())
|
||||
{
|
||||
all_acc |= acc;
|
||||
if (!all_acc_seen && aut->acc().accepting(all_acc))
|
||||
{
|
||||
all_acc_seen = true;
|
||||
if (debug)
|
||||
os << "all acceptance conditions ("
|
||||
<< aut->acc().format(all_acc)
|
||||
<< ") have been seen\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
s->destroy();
|
||||
if (!aut->acc().accepting(all_acc))
|
||||
{
|
||||
if (debug)
|
||||
os << "ERROR: The cycle's acceptance conditions ("
|
||||
<< aut->acc().format(all_acc)
|
||||
<< ") do not\nmatch those of the automaton ("
|
||||
<< aut->acc().format(aut->acc().all_sets())
|
||||
<< ")\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
state_map::const_iterator o = seen.begin();
|
||||
while (o != seen.end())
|
||||
{
|
||||
// Advance the iterator before deleting the "key" pointer.
|
||||
const state* ptr = o->first;
|
||||
++o;
|
||||
ptr->destroy();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
twa_run::as_twa() const
|
||||
{
|
||||
auto d = aut->get_dict();
|
||||
auto res = make_twa_graph(d);
|
||||
res->copy_ap_of(aut);
|
||||
res->copy_acceptance_of(aut);
|
||||
|
||||
const state* s = aut->get_init_state();
|
||||
unsigned src;
|
||||
unsigned dst;
|
||||
const twa_run::steps* l;
|
||||
acc_cond::mark_t seen_acc = 0U;
|
||||
|
||||
typedef std::unordered_map<const state*, unsigned,
|
||||
state_ptr_hash, state_ptr_equal> state_map;
|
||||
state_map seen;
|
||||
|
||||
if (prefix.empty())
|
||||
l = &cycle;
|
||||
else
|
||||
l = &prefix;
|
||||
|
||||
twa_run::steps::const_iterator i = l->begin();
|
||||
|
||||
assert(s->compare(i->s) == 0);
|
||||
src = res->new_state();
|
||||
seen.emplace(i->s, src);
|
||||
|
||||
for (; i != l->end();)
|
||||
{
|
||||
// expected outgoing transition
|
||||
bdd label = i->label;
|
||||
acc_cond::mark_t acc = i->acc;
|
||||
|
||||
// compute the next expected state
|
||||
const state* next;
|
||||
++i;
|
||||
if (i != l->end())
|
||||
{
|
||||
next = i->s;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (l == &prefix)
|
||||
{
|
||||
l = &cycle;
|
||||
i = l->begin();
|
||||
}
|
||||
next = l->begin()->s;
|
||||
}
|
||||
|
||||
// browse the actual outgoing transitions and
|
||||
// look for next;
|
||||
const state* the_next = nullptr;
|
||||
for (auto j: aut->succ(s))
|
||||
{
|
||||
if (j->cond() != label
|
||||
|| j->acc() != acc)
|
||||
continue;
|
||||
|
||||
const state* s2 = j->dst();
|
||||
if (s2->compare(next) == 0)
|
||||
{
|
||||
the_next = s2;
|
||||
break;
|
||||
}
|
||||
s2->destroy();
|
||||
}
|
||||
assert(res);
|
||||
s->destroy();
|
||||
s = the_next;
|
||||
|
||||
|
||||
auto p = seen.emplace(next, 0);
|
||||
if (p.second)
|
||||
p.first->second = res->new_state();
|
||||
dst = p.first->second;
|
||||
|
||||
res->new_edge(src, dst, label, acc);
|
||||
src = dst;
|
||||
|
||||
// Sum acceptance conditions.
|
||||
if (l == &cycle && i != l->begin())
|
||||
seen_acc |= acc;
|
||||
}
|
||||
|
||||
s->destroy();
|
||||
|
||||
assert(aut->acc().accepting(seen_acc));
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,341 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Developpement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 <map>
|
||||
#include <list>
|
||||
#include <iosfwd>
|
||||
#include <bddx.h>
|
||||
#include "misc/optionmap.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "emptiness_stats.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
struct twa_run;
|
||||
typedef std::shared_ptr<twa_run> twa_run_ptr;
|
||||
typedef std::shared_ptr<const twa_run> const_twa_run_ptr;
|
||||
|
||||
/// \addtogroup emptiness_check Emptiness-checks
|
||||
/// \ingroup twa_algorithms
|
||||
///
|
||||
/// All emptiness-check algorithms follow the same interface.
|
||||
/// Basically once you have constructed an instance of
|
||||
/// spot::emptiness_check (by instantiating a subclass, or calling a
|
||||
/// functions construct such instance; see \ref
|
||||
/// emptiness_check_algorithms "this list"), you should call
|
||||
/// spot::emptiness_check::check() to check the automaton.
|
||||
///
|
||||
/// If spot::emptiness_check::check() returns 0, then the automaton
|
||||
/// was found empty. Otherwise the automaton accepts some run.
|
||||
/// (Beware that some algorithms---those using bit-state
|
||||
/// hashing---may found the automaton to be empty even if it is not
|
||||
/// actually empty.)
|
||||
///
|
||||
/// When spot::emptiness_check::check() does not return 0, it
|
||||
/// returns an instance of spot::emptiness_check_result. You can
|
||||
/// try to call spot::emptiness_check_result::accepting_run() to
|
||||
/// obtain an accepting run. For some emptiness-check algorithms,
|
||||
/// spot::emptiness_check_result::accepting_run() will require some
|
||||
/// extra computation. Most emptiness-check algorithms are able to
|
||||
/// return such an accepting run, however this is not mandatory and
|
||||
/// spot::emptiness_check_result::accepting_run() can return 0 (this
|
||||
/// does not means by anyway that no accepting run exist).
|
||||
///
|
||||
/// The acceptance run returned by
|
||||
/// spot::emptiness_check_result::accepting_run(), if any, is of
|
||||
/// type spot::twa_run. \ref twa_run "This page" gathers existing
|
||||
/// operations on these objects.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// \brief The result of an emptiness check.
|
||||
///
|
||||
/// Instances of these class should not last longer than the
|
||||
/// instances of emptiness_check that produced them as they
|
||||
/// may reference data internal to the check.
|
||||
class SPOT_API emptiness_check_result
|
||||
{
|
||||
public:
|
||||
emptiness_check_result(const const_twa_ptr& a,
|
||||
option_map o = option_map())
|
||||
: a_(a), o_(o)
|
||||
{
|
||||
}
|
||||
|
||||
virtual
|
||||
~emptiness_check_result()
|
||||
{
|
||||
}
|
||||
|
||||
/// \brief Return a run accepted by the automata passed to
|
||||
/// the emptiness check.
|
||||
///
|
||||
/// This method might actually compute the acceptance run. (Not
|
||||
/// all emptiness check algorithms actually produce a
|
||||
/// counter-example as a side-effect of checking emptiness, some
|
||||
/// need some post-processing.)
|
||||
///
|
||||
/// This can also return 0 if the emptiness check algorithm
|
||||
/// cannot produce a counter example (that does not mean there
|
||||
/// is no counter-example; the mere existence of an instance of
|
||||
/// this class asserts the existence of a counter-example).
|
||||
virtual twa_run_ptr accepting_run();
|
||||
|
||||
/// The automaton on which an accepting_run() was found.
|
||||
const const_twa_ptr&
|
||||
automaton() const
|
||||
{
|
||||
return a_;
|
||||
}
|
||||
|
||||
/// Return the options parametrizing how the accepting run is computed.
|
||||
const option_map&
|
||||
options() const
|
||||
{
|
||||
return o_;
|
||||
}
|
||||
|
||||
/// Modify the algorithm options.
|
||||
const char* parse_options(char* options);
|
||||
|
||||
/// Return statistics, if available.
|
||||
virtual const unsigned_statistics* statistics() const;
|
||||
|
||||
protected:
|
||||
/// Notify option updates.
|
||||
virtual void options_updated(const option_map& old);
|
||||
|
||||
const_twa_ptr a_; ///< The automaton.
|
||||
option_map o_; ///< The options.
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<emptiness_check_result> emptiness_check_result_ptr;
|
||||
|
||||
/// Common interface to emptiness check algorithms.
|
||||
class SPOT_API emptiness_check:
|
||||
public std::enable_shared_from_this<emptiness_check>
|
||||
{
|
||||
public:
|
||||
emptiness_check(const const_twa_ptr& a, option_map o = option_map())
|
||||
: a_(a), o_(o)
|
||||
{
|
||||
}
|
||||
virtual ~emptiness_check();
|
||||
|
||||
/// The automaton that this emptiness-check inspects.
|
||||
const const_twa_ptr&
|
||||
automaton() const
|
||||
{
|
||||
return a_;
|
||||
}
|
||||
|
||||
/// Return the options parametrizing how the emptiness check is realized.
|
||||
const option_map&
|
||||
options() const
|
||||
{
|
||||
return o_;
|
||||
}
|
||||
|
||||
/// Modify the algorithm options.
|
||||
const char* parse_options(char* options);
|
||||
|
||||
/// Return false iff accepting_run() can return 0 for non-empty automata.
|
||||
virtual bool safe() const;
|
||||
|
||||
/// \brief Check whether the automaton contain an accepting run.
|
||||
///
|
||||
/// Return 0 if the automaton accepts no run. Return an instance
|
||||
/// of emptiness_check_result otherwise. This instance might
|
||||
/// allow to obtain one sample acceptance run. The result has to
|
||||
/// be destroyed before the emptiness_check instance that
|
||||
/// generated it.
|
||||
///
|
||||
/// Some emptiness_check algorithms may allow check() to be called
|
||||
/// several time, but generally you should not assume that.
|
||||
///
|
||||
/// Some emptiness_check algorithms, especially those using bit state
|
||||
/// hashing may return 0 even if the automaton is not empty.
|
||||
/// \see safe()
|
||||
virtual emptiness_check_result_ptr check() = 0;
|
||||
|
||||
/// Return statistics, if available.
|
||||
virtual const unsigned_statistics* statistics() const;
|
||||
|
||||
/// Return emptiness check statistics, if available.
|
||||
virtual const ec_statistics* emptiness_check_statistics() const;
|
||||
|
||||
/// Print statistics, if any.
|
||||
virtual std::ostream& print_stats(std::ostream& os) const;
|
||||
|
||||
/// Notify option updates.
|
||||
virtual void options_updated(const option_map& old);
|
||||
|
||||
protected:
|
||||
const_twa_ptr a_; ///< The automaton.
|
||||
option_map o_; ///< The options
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<emptiness_check> emptiness_check_ptr;
|
||||
|
||||
class emptiness_check_instantiator;
|
||||
typedef std::shared_ptr<emptiness_check_instantiator>
|
||||
emptiness_check_instantiator_ptr;
|
||||
|
||||
// Dynamically create emptiness checks. Given their name and options.
|
||||
class SPOT_API emptiness_check_instantiator
|
||||
{
|
||||
public:
|
||||
/// Actually instantiate the emptiness check, for \a a.
|
||||
emptiness_check_ptr instantiate(const const_twa_ptr& a) const;
|
||||
|
||||
/// Accessor to the options.
|
||||
/// @{
|
||||
const option_map&
|
||||
options() const
|
||||
{
|
||||
return o_;
|
||||
}
|
||||
|
||||
option_map&
|
||||
options()
|
||||
{
|
||||
return o_;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \brief Minimum number of acceptance conditions supported by
|
||||
/// the emptiness check.
|
||||
unsigned int min_acceptance_conditions() const;
|
||||
|
||||
/// \brief Maximum number of acceptance conditions supported by
|
||||
/// the emptiness check.
|
||||
///
|
||||
/// \return \c -1U if no upper bound exists.
|
||||
unsigned int max_acceptance_conditions() const;
|
||||
protected:
|
||||
emptiness_check_instantiator(option_map o, void* i);
|
||||
|
||||
option_map o_;
|
||||
void *info_;
|
||||
};
|
||||
/// @}
|
||||
|
||||
/// \brief Create an emptiness-check instantiator, given the name
|
||||
/// of an emptiness check.
|
||||
///
|
||||
/// \a name should have the form \c "name" or \c "name(options)".
|
||||
///
|
||||
/// On error, the function returns 0. If the name of the algorithm
|
||||
/// was unknown, \c *err will be set to \c name. If some fragment of
|
||||
/// the options could not be parsed, \c *err will point to that
|
||||
/// fragment.
|
||||
SPOT_API emptiness_check_instantiator_ptr
|
||||
make_emptiness_check_instantiator(const char* name, const char** err);
|
||||
|
||||
|
||||
/// \addtogroup emptiness_check_algorithms Emptiness-check algorithms
|
||||
/// \ingroup emptiness_check
|
||||
|
||||
/// \addtogroup twa_run TωA runs and supporting functions
|
||||
/// \ingroup emptiness_check
|
||||
/// @{
|
||||
|
||||
/// An accepted run, for a twa.
|
||||
struct SPOT_API twa_run final
|
||||
{
|
||||
struct step {
|
||||
const state* s;
|
||||
bdd label;
|
||||
acc_cond::mark_t acc;
|
||||
|
||||
step(const state* s, bdd label, acc_cond::mark_t acc)
|
||||
: s(s), label(label), acc(acc)
|
||||
{
|
||||
}
|
||||
step()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::list<step> steps;
|
||||
|
||||
steps prefix;
|
||||
steps cycle;
|
||||
const_twa_ptr aut;
|
||||
|
||||
~twa_run();
|
||||
twa_run(const const_twa_ptr& aut)
|
||||
: aut(aut)
|
||||
{
|
||||
}
|
||||
twa_run(const twa_run& run);
|
||||
twa_run& operator=(const twa_run& run);
|
||||
|
||||
/// \brief Reduce an accepting run.
|
||||
///
|
||||
/// Return a run which is still accepting for <code>aut</code>,
|
||||
/// but is no longer than this one.
|
||||
twa_run_ptr reduce() const;
|
||||
|
||||
/// \brief Replay a run.
|
||||
///
|
||||
/// This is similar to <code>os << run;</code>, except that the
|
||||
/// run is actually replayed on the automaton while it is printed.
|
||||
/// Doing so makes it possible to display transition annotations
|
||||
/// (returned by spot::twa::transition_annotation()). The output
|
||||
/// will stop if the run cannot be completed.
|
||||
///
|
||||
/// \param os the stream on which the replay should be traced
|
||||
/// \param debug if set the output will be more verbose and extra
|
||||
/// debugging informations will be output on failure
|
||||
/// \return true iff the run could be completed
|
||||
bool replay(std::ostream& os, bool debug = false) const;
|
||||
|
||||
/// \brief Return a twa_graph_ptr corresponding to \a run
|
||||
///
|
||||
/// Identical states are merged.
|
||||
twa_graph_ptr as_twa() const;
|
||||
|
||||
/// \brief Display a twa_run.
|
||||
///
|
||||
/// Output the prefix and cycle parts of the twa_run \a run on \a os.
|
||||
///
|
||||
/// The automaton object (stored by \a run) is used only to format
|
||||
/// the states, and to know how to print the BDDs describing the
|
||||
/// conditions and acceptance conditions of the run; it is
|
||||
/// <b>not</b> used to replay the run. In other words this
|
||||
/// function will work even if the twa_run you are trying to print
|
||||
/// appears to connect states that are not connected.
|
||||
///
|
||||
/// This is unlike replay_twa_run(), which will ensure the run
|
||||
/// actually exists in the automaton (and will also display any
|
||||
/// transition annotation).
|
||||
SPOT_API
|
||||
friend std::ostream& operator<<(std::ostream& os, const twa_run& run);
|
||||
};
|
||||
/// @}
|
||||
|
||||
/// \addtogroup emptiness_check_stats Emptiness-check statistics
|
||||
/// \ingroup emptiness_check
|
||||
}
|
||||
|
|
@ -1,273 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et Développement de
|
||||
// l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 <cassert>
|
||||
#include <map>
|
||||
#include "misc/ltstr.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
/// \addtogroup emptiness_check_stats
|
||||
/// @{
|
||||
|
||||
struct unsigned_statistics
|
||||
{
|
||||
virtual
|
||||
~unsigned_statistics()
|
||||
{
|
||||
}
|
||||
|
||||
unsigned
|
||||
get(const char* str) const
|
||||
{
|
||||
auto i = stats.find(str);
|
||||
assert(i != stats.end());
|
||||
return (this->*i->second)();
|
||||
}
|
||||
|
||||
typedef unsigned (unsigned_statistics::*unsigned_fun)() const;
|
||||
typedef std::map<const char*, unsigned_fun, char_ptr_less_than> stats_map;
|
||||
stats_map stats;
|
||||
};
|
||||
|
||||
/// \brief comparable statistics
|
||||
///
|
||||
/// This must be built from a spot::unsigned_statistics. But unlike
|
||||
/// spot::unsigned_statistics, it supports equality and inequality tests.
|
||||
/// (It's the only operations it supports, BTW.)
|
||||
class unsigned_statistics_copy
|
||||
{
|
||||
public:
|
||||
unsigned_statistics_copy()
|
||||
: set(false)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned_statistics_copy(const unsigned_statistics& o)
|
||||
: set(false)
|
||||
{
|
||||
seteq(o);
|
||||
}
|
||||
|
||||
bool
|
||||
seteq(const unsigned_statistics& o)
|
||||
{
|
||||
if (!set)
|
||||
{
|
||||
for (auto& i: o.stats)
|
||||
stats[i.first] = (o.*i.second)();
|
||||
set = true;
|
||||
return true;
|
||||
}
|
||||
if (*this == o)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef std::map<const char*, unsigned, char_ptr_less_than> stats_map;
|
||||
stats_map stats;
|
||||
|
||||
|
||||
bool
|
||||
operator==(const unsigned_statistics_copy& o) const
|
||||
{
|
||||
for (auto& i: stats)
|
||||
{
|
||||
auto i2 = o.stats.find(i.first);
|
||||
if (i2 == o.stats.end())
|
||||
return false;
|
||||
if (i.second != i2->second)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(const unsigned_statistics_copy& o) const
|
||||
{
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
bool set;
|
||||
};
|
||||
|
||||
/// \brief Emptiness-check statistics
|
||||
///
|
||||
/// Implementations of spot::emptiness_check may also implement
|
||||
/// this interface. Try to dynamic_cast the spot::emptiness_check
|
||||
/// pointer to know whether these statistics are available.
|
||||
class ec_statistics: public unsigned_statistics
|
||||
{
|
||||
public :
|
||||
ec_statistics()
|
||||
: states_(0), transitions_(0), depth_(0), max_depth_(0)
|
||||
{
|
||||
stats["states"] =
|
||||
static_cast<unsigned_statistics::unsigned_fun>(&ec_statistics::states);
|
||||
stats["transitions"] =
|
||||
static_cast<unsigned_statistics::unsigned_fun>
|
||||
(&ec_statistics::transitions);
|
||||
stats["max. depth"] =
|
||||
static_cast<unsigned_statistics::unsigned_fun>
|
||||
(&ec_statistics::max_depth);
|
||||
}
|
||||
|
||||
void
|
||||
set_states(unsigned n)
|
||||
{
|
||||
states_ = n;
|
||||
}
|
||||
|
||||
void
|
||||
inc_states()
|
||||
{
|
||||
++states_;
|
||||
}
|
||||
|
||||
void
|
||||
inc_transitions()
|
||||
{
|
||||
++transitions_;
|
||||
}
|
||||
|
||||
void
|
||||
inc_depth(unsigned n = 1)
|
||||
{
|
||||
depth_ += n;
|
||||
if (depth_ > max_depth_)
|
||||
max_depth_ = depth_;
|
||||
}
|
||||
|
||||
void
|
||||
dec_depth(unsigned n = 1)
|
||||
{
|
||||
assert(depth_ >= n);
|
||||
depth_ -= n;
|
||||
}
|
||||
|
||||
unsigned
|
||||
states() const
|
||||
{
|
||||
return states_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
transitions() const
|
||||
{
|
||||
return transitions_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
max_depth() const
|
||||
{
|
||||
return max_depth_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
depth() const
|
||||
{
|
||||
return depth_;
|
||||
}
|
||||
|
||||
private :
|
||||
unsigned states_; /// number of disctint visited states
|
||||
unsigned transitions_; /// number of visited transitions
|
||||
unsigned depth_; /// maximal depth of the stack(s)
|
||||
unsigned max_depth_; /// maximal depth of the stack(s)
|
||||
};
|
||||
|
||||
/// \brief Accepting Run Search statistics.
|
||||
///
|
||||
/// Implementations of spot::emptiness_check_result may also implement
|
||||
/// this interface. Try to dynamic_cast the spot::emptiness_check_result
|
||||
/// pointer to know whether these statistics are available.
|
||||
class ars_statistics: public unsigned_statistics
|
||||
{
|
||||
public:
|
||||
ars_statistics()
|
||||
: prefix_states_(0), cycle_states_(0)
|
||||
{
|
||||
stats["(non unique) states for prefix"] =
|
||||
static_cast<unsigned_statistics::unsigned_fun>
|
||||
(&ars_statistics::ars_prefix_states);
|
||||
stats["(non unique) states for cycle"] =
|
||||
static_cast<unsigned_statistics::unsigned_fun>
|
||||
(&ars_statistics::ars_cycle_states);
|
||||
}
|
||||
|
||||
void
|
||||
inc_ars_prefix_states()
|
||||
{
|
||||
++prefix_states_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
ars_prefix_states() const
|
||||
{
|
||||
return prefix_states_;
|
||||
}
|
||||
|
||||
void
|
||||
inc_ars_cycle_states()
|
||||
{
|
||||
++cycle_states_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
ars_cycle_states() const
|
||||
{
|
||||
return cycle_states_;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned prefix_states_; /// states visited to construct the prefix
|
||||
unsigned cycle_states_; /// states visited to construct the cycle
|
||||
};
|
||||
|
||||
/// \brief Accepting Cycle Search Space statistics
|
||||
///
|
||||
/// Implementations of spot::emptiness_check_result may also implement
|
||||
/// this interface. Try to dynamic_cast the spot::emptiness_check_result
|
||||
/// pointer to know whether these statistics are available.
|
||||
class acss_statistics: public ars_statistics
|
||||
{
|
||||
public:
|
||||
acss_statistics()
|
||||
{
|
||||
stats["search space states"] =
|
||||
static_cast<unsigned_statistics::unsigned_fun>
|
||||
(&acss_statistics::acss_states);
|
||||
}
|
||||
|
||||
virtual
|
||||
~acss_statistics()
|
||||
{
|
||||
}
|
||||
|
||||
/// Number of states in the search space for the accepting cycle.
|
||||
virtual unsigned acss_states() const = 0;
|
||||
};
|
||||
/// @}
|
||||
}
|
||||
6
src/twaalgos/gtec/.gitignore
vendored
6
src/twaalgos/gtec/.gitignore
vendored
|
|
@ -1,6 +0,0 @@
|
|||
.deps
|
||||
.libs
|
||||
*.lo
|
||||
*.la
|
||||
Makefile
|
||||
Makefile.in
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
## Copyright (C) 2011, 2013, 2014 Laboratoire de Recherche et Developpement
|
||||
## 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/>.
|
||||
|
||||
AM_CPPFLAGS = -I$(srcdir)/../.. -I../.. $(BUDDY_CPPFLAGS)
|
||||
AM_CXXFLAGS = $(WARNING_CXXFLAGS)
|
||||
|
||||
gtecdir = $(pkgincludedir)/tgbaalgos/gtec
|
||||
|
||||
gtec_HEADERS = \
|
||||
ce.hh \
|
||||
gtec.hh \
|
||||
sccstack.hh \
|
||||
status.hh
|
||||
|
||||
noinst_LTLIBRARIES = libgtec.la
|
||||
libgtec_la_SOURCES = \
|
||||
ce.cc \
|
||||
gtec.cc \
|
||||
sccstack.cc \
|
||||
status.cc
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2011, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 "ce.hh"
|
||||
#include "twaalgos/bfssteps.hh"
|
||||
#include "misc/hash.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class shortest_path: public bfs_steps
|
||||
{
|
||||
public:
|
||||
shortest_path(const state_set* t,
|
||||
const std::shared_ptr<const couvreur99_check_status>& ecs,
|
||||
couvreur99_check_result* r)
|
||||
: bfs_steps(ecs->aut), target(t), ecs(ecs), r(r)
|
||||
{
|
||||
}
|
||||
|
||||
const state*
|
||||
search(const state* start, twa_run::steps& l)
|
||||
{
|
||||
return this->bfs_steps::search(filter(start), l);
|
||||
}
|
||||
|
||||
const state*
|
||||
filter(const state* s)
|
||||
{
|
||||
r->inc_ars_prefix_states();
|
||||
auto i = ecs->h.find(s);
|
||||
s->destroy();
|
||||
// Ignore unknown states ...
|
||||
if (i == ecs->h.end())
|
||||
return nullptr;
|
||||
// ... as well as dead states.
|
||||
if (i->second == -1)
|
||||
return nullptr;
|
||||
return i->first;
|
||||
}
|
||||
|
||||
bool
|
||||
match(twa_run::step&, const state* dest)
|
||||
{
|
||||
return target->find(dest) != target->end();
|
||||
}
|
||||
|
||||
private:
|
||||
state_set seen;
|
||||
const state_set* target;
|
||||
std::shared_ptr<const couvreur99_check_status> ecs;
|
||||
couvreur99_check_result* r;
|
||||
};
|
||||
}
|
||||
|
||||
couvreur99_check_result::couvreur99_check_result
|
||||
(const std::shared_ptr<const couvreur99_check_status>& ecs,
|
||||
option_map o)
|
||||
: emptiness_check_result(ecs->aut, o), ecs_(ecs)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned
|
||||
couvreur99_check_result::acss_states() const
|
||||
{
|
||||
int scc_root = ecs_->root.top().index;
|
||||
unsigned count = 0;
|
||||
for (auto i: ecs_->h)
|
||||
if (i.second >= scc_root)
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
twa_run_ptr
|
||||
couvreur99_check_result::accepting_run()
|
||||
{
|
||||
run_ = std::make_shared<twa_run>(ecs_->aut);
|
||||
|
||||
assert(!ecs_->root.empty());
|
||||
|
||||
// Compute an accepting cycle.
|
||||
accepting_cycle();
|
||||
|
||||
// Compute the prefix: it's the shortest path from the initial
|
||||
// state of the automata to any state of the cycle.
|
||||
|
||||
// Register all states from the cycle as target of the BFS.
|
||||
state_set ss;
|
||||
for (twa_run::steps::const_iterator i = run_->cycle.begin();
|
||||
i != run_->cycle.end(); ++i)
|
||||
ss.insert(i->s);
|
||||
shortest_path shpath(&ss, ecs_, this);
|
||||
|
||||
const state* prefix_start = ecs_->aut->get_init_state();
|
||||
// There are two cases: either the initial state is already on
|
||||
// the cycle, or it is not. If it is, we will have to rotate
|
||||
// the cycle so it begins on this position. Otherwise we will shift
|
||||
// the cycle so it begins on the state that follows the prefix.
|
||||
// cycle_entry_point is that state.
|
||||
const state* cycle_entry_point;
|
||||
state_set::const_iterator ps = ss.find(prefix_start);
|
||||
if (ps != ss.end())
|
||||
{
|
||||
// The initial state is on the cycle.
|
||||
prefix_start->destroy();
|
||||
cycle_entry_point = *ps;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This initial state is outside the cycle. Compute the prefix.
|
||||
cycle_entry_point = shpath.search(prefix_start, run_->prefix);
|
||||
}
|
||||
|
||||
// Locate cycle_entry_point on the cycle.
|
||||
twa_run::steps::iterator cycle_ep_it;
|
||||
for (cycle_ep_it = run_->cycle.begin();
|
||||
cycle_ep_it != run_->cycle.end()
|
||||
&& cycle_entry_point->compare(cycle_ep_it->s); ++cycle_ep_it)
|
||||
continue;
|
||||
assert(cycle_ep_it != run_->cycle.end());
|
||||
|
||||
// Now shift the cycle so it starts on cycle_entry_point.
|
||||
run_->cycle.splice(run_->cycle.end(), run_->cycle,
|
||||
run_->cycle.begin(), cycle_ep_it);
|
||||
|
||||
return run_;
|
||||
}
|
||||
|
||||
void
|
||||
couvreur99_check_result::accepting_cycle()
|
||||
{
|
||||
acc_cond::mark_t acc_to_traverse =
|
||||
ecs_->aut->acc().accepting_sets(ecs_->root.top().condition);
|
||||
// Compute an accepting cycle using successive BFS that are
|
||||
// restarted from the point reached after we have discovered a
|
||||
// transition with a new acceptance conditions.
|
||||
//
|
||||
// This idea is taken from Product<T>::findWitness in LBTT 1.1.2,
|
||||
// which in turn is probably inspired from
|
||||
// @Article{ latvala.00.fi,
|
||||
// author = {Timo Latvala and Keijo Heljanko},
|
||||
// title = {Coping With Strong Fairness},
|
||||
// journal = {Fundamenta Informaticae},
|
||||
// year = {2000},
|
||||
// volume = {43},
|
||||
// number = {1--4},
|
||||
// pages = {1--19},
|
||||
// publisher = {IOS Press}
|
||||
// }
|
||||
const state* substart = ecs_->cycle_seed;
|
||||
do
|
||||
{
|
||||
struct scc_bfs: bfs_steps
|
||||
{
|
||||
const couvreur99_check_status* ecs;
|
||||
couvreur99_check_result* r;
|
||||
acc_cond::mark_t& acc_to_traverse;
|
||||
int scc_root;
|
||||
|
||||
scc_bfs(const couvreur99_check_status* ecs,
|
||||
couvreur99_check_result* r, acc_cond::mark_t& acc_to_traverse)
|
||||
: bfs_steps(ecs->aut), ecs(ecs), r(r),
|
||||
acc_to_traverse(acc_to_traverse),
|
||||
scc_root(ecs->root.top().index)
|
||||
{
|
||||
}
|
||||
|
||||
virtual const state*
|
||||
filter(const state* s)
|
||||
{
|
||||
auto i = ecs->h.find(s);
|
||||
s->destroy();
|
||||
// Ignore unknown states.
|
||||
if (i == ecs->h.end())
|
||||
return nullptr;
|
||||
// Stay in the final SCC.
|
||||
if (i->second < scc_root)
|
||||
return nullptr;
|
||||
r->inc_ars_cycle_states();
|
||||
return i->first;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
match(twa_run::step& st, const state* s)
|
||||
{
|
||||
acc_cond::mark_t less_acc =
|
||||
acc_to_traverse - st.acc;
|
||||
if (less_acc != acc_to_traverse
|
||||
|| (acc_to_traverse == 0U
|
||||
&& s == ecs->cycle_seed))
|
||||
{
|
||||
acc_to_traverse = less_acc;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} b(ecs_.get(), this, acc_to_traverse);
|
||||
|
||||
substart = b.search(substart, run_->cycle);
|
||||
assert(substart);
|
||||
}
|
||||
while (acc_to_traverse != 0U || substart != ecs_->cycle_seed);
|
||||
}
|
||||
|
||||
void
|
||||
couvreur99_check_result::print_stats(std::ostream& os) const
|
||||
{
|
||||
ecs_->print_stats(os);
|
||||
// FIXME: This is bogusly assuming run_ exists. (Even if we
|
||||
// created it, the user might have deleted it.)
|
||||
os << run_->prefix.size() << " states in run_->prefix" << std::endl;
|
||||
os << run_->cycle.size() << " states in run_->cycle" << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014 Laboratoire de Recherche et Développement de
|
||||
// l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 "status.hh"
|
||||
#include "twaalgos/emptiness.hh"
|
||||
#include "twaalgos/emptiness_stats.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// Compute a counter example from a spot::couvreur99_check_status
|
||||
class SPOT_API couvreur99_check_result:
|
||||
public emptiness_check_result,
|
||||
public acss_statistics
|
||||
{
|
||||
public:
|
||||
couvreur99_check_result(const
|
||||
std::shared_ptr<const couvreur99_check_status>& ecs,
|
||||
option_map o = option_map());
|
||||
|
||||
virtual twa_run_ptr accepting_run();
|
||||
|
||||
void print_stats(std::ostream& os) const;
|
||||
|
||||
virtual unsigned acss_states() const;
|
||||
|
||||
protected:
|
||||
/// Called by accepting_run() to find a cycle which traverses all
|
||||
/// acceptance conditions in the accepted SCC.
|
||||
void accepting_cycle();
|
||||
|
||||
private:
|
||||
std::shared_ptr<const couvreur99_check_status> ecs_;
|
||||
twa_run_ptr run_;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,617 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2011, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement 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/>.
|
||||
|
||||
// #define TRACE
|
||||
|
||||
#include <iostream>
|
||||
#ifdef TRACE
|
||||
#define trace std::cerr
|
||||
#else
|
||||
#define trace while (0) std::cerr
|
||||
#endif
|
||||
|
||||
#include "gtec.hh"
|
||||
#include "ce.hh"
|
||||
#include "misc/memusage.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
typedef std::pair<const spot::state*, twa_succ_iterator*> pair_state_iter;
|
||||
}
|
||||
|
||||
couvreur99_check::couvreur99_check(const const_twa_ptr& a, option_map o)
|
||||
: emptiness_check(a, o),
|
||||
removed_components(0)
|
||||
{
|
||||
poprem_ = o.get("poprem", 1);
|
||||
ecs_ = std::make_shared<couvreur99_check_status>(a);
|
||||
stats["removed components"] =
|
||||
static_cast<spot::unsigned_statistics::unsigned_fun>
|
||||
(&couvreur99_check::get_removed_components);
|
||||
stats["vmsize"] =
|
||||
static_cast<spot::unsigned_statistics::unsigned_fun>
|
||||
(&couvreur99_check::get_vmsize);
|
||||
}
|
||||
|
||||
couvreur99_check::~couvreur99_check()
|
||||
{
|
||||
}
|
||||
|
||||
unsigned
|
||||
couvreur99_check::get_removed_components() const
|
||||
{
|
||||
return removed_components;
|
||||
}
|
||||
|
||||
unsigned
|
||||
couvreur99_check::get_vmsize() const
|
||||
{
|
||||
int size = memusage();
|
||||
if (size > 0)
|
||||
return size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
couvreur99_check::remove_component(const state* from)
|
||||
{
|
||||
++removed_components;
|
||||
// If rem has been updated, removing states is very easy.
|
||||
if (poprem_)
|
||||
{
|
||||
assert(!ecs_->root.rem().empty());
|
||||
dec_depth(ecs_->root.rem().size());
|
||||
for (auto i: ecs_->root.rem())
|
||||
ecs_->h[i] = -1;
|
||||
// ecs_->root.rem().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from H all states which are reachable from state FROM.
|
||||
|
||||
// Stack of iterators towards states to remove.
|
||||
std::stack<twa_succ_iterator*> to_remove;
|
||||
|
||||
// Remove FROM itself, and prepare to remove its successors.
|
||||
// (FROM should be in H, otherwise it means all reachable
|
||||
// states from FROM have already been removed and there is no
|
||||
// point in calling remove_component.)
|
||||
ecs_->h[from] = -1;
|
||||
twa_succ_iterator* i = ecs_->aut->succ_iter(from);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Remove each destination of this iterator.
|
||||
if (i->first())
|
||||
do
|
||||
{
|
||||
inc_transitions();
|
||||
|
||||
const state* s = i->dst();
|
||||
auto j = ecs_->h.find(s);
|
||||
assert(j != ecs_->h.end());
|
||||
s->destroy();
|
||||
|
||||
if (j->second != -1)
|
||||
{
|
||||
j->second = -1;
|
||||
to_remove.push(ecs_->aut->succ_iter(j->first));
|
||||
}
|
||||
}
|
||||
while (i->next());
|
||||
ecs_->aut->release_iter(i);
|
||||
if (to_remove.empty())
|
||||
break;
|
||||
i = to_remove.top();
|
||||
to_remove.pop();
|
||||
}
|
||||
}
|
||||
|
||||
emptiness_check_result_ptr
|
||||
couvreur99_check::check()
|
||||
{
|
||||
{
|
||||
auto acc = ecs_->aut->acc();
|
||||
if (acc.get_acceptance().is_ff())
|
||||
return nullptr;
|
||||
if (acc.uses_fin_acceptance())
|
||||
throw std::runtime_error
|
||||
("Fin acceptance is not supported by couvreur99()");
|
||||
}
|
||||
|
||||
// We use five main data in this algorithm:
|
||||
// * couvreur99_check::root, a stack of strongly connected components (SCC),
|
||||
// * couvreur99_check::h, a hash of all visited nodes, with their order,
|
||||
// (it is called "Hash" in Couvreur's paper)
|
||||
// * arc, a stack of acceptance conditions between each of these SCC,
|
||||
std::stack<acc_cond::mark_t> arc;
|
||||
// * 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 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;
|
||||
|
||||
// Setup depth-first search from the initial state.
|
||||
{
|
||||
const state* init = ecs_->aut->get_init_state();
|
||||
ecs_->h[init] = 1;
|
||||
ecs_->root.push(1);
|
||||
arc.push(0U);
|
||||
twa_succ_iterator* iter = ecs_->aut->succ_iter(init);
|
||||
iter->first();
|
||||
todo.emplace(init, iter);
|
||||
inc_depth();
|
||||
}
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
assert(ecs_->root.size() == arc.size());
|
||||
|
||||
// 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.
|
||||
const state* curr = todo.top().first;
|
||||
|
||||
// Backtrack TODO.
|
||||
todo.pop();
|
||||
dec_depth();
|
||||
|
||||
// If poprem is used, fill rem with any component removed,
|
||||
// so that remove_component() does not have to traverse
|
||||
// the SCC again.
|
||||
auto i = ecs_->h.find(curr);
|
||||
assert(i != ecs_->h.end());
|
||||
if (poprem_)
|
||||
{
|
||||
ecs_->root.rem().push_front(i->first);
|
||||
inc_depth();
|
||||
}
|
||||
// When backtracking the root of an SCC, we must also
|
||||
// remove that SCC from the ARC/ROOT stacks. We must
|
||||
// discard from H all reachable states from this SCC.
|
||||
assert(!ecs_->root.empty());
|
||||
if (ecs_->root.top().index == i->second)
|
||||
{
|
||||
assert(!arc.empty());
|
||||
arc.pop();
|
||||
remove_component(curr);
|
||||
ecs_->root.pop();
|
||||
}
|
||||
ecs_->aut->release_iter(succ);
|
||||
// Do not destroy CURR: it is a key in H.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a successor to look at.
|
||||
inc_transitions();
|
||||
// Fetch the values (destination state, acceptance conditions
|
||||
// of the arc) we are interested in...
|
||||
const state* dest = succ->dst();
|
||||
acc_cond::mark_t acc = 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?
|
||||
auto p = ecs_->h.emplace(dest, num + 1);
|
||||
if (p.second)
|
||||
{
|
||||
// Yes. Bump number, stack the stack, and register its
|
||||
// successors for later processing.
|
||||
ecs_->root.push(++num);
|
||||
arc.push(acc);
|
||||
twa_succ_iterator* iter = ecs_->aut->succ_iter(dest);
|
||||
iter->first();
|
||||
todo.emplace(dest, iter);
|
||||
inc_depth();
|
||||
continue;
|
||||
}
|
||||
dest->destroy();
|
||||
|
||||
// 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 SCC. Any such
|
||||
// non-dead SCC has necessarily been crossed by our path to
|
||||
// this state: there is a state S2 in our path which belongs
|
||||
// to this SCC too. We are going to merge all states between
|
||||
// this S1 and S2 into this SCC.
|
||||
//
|
||||
// This merge is easy to do because the order of the SCC in
|
||||
// ROOT is ascending: we just have to merge all SCCs from the
|
||||
// top of ROOT that have an index greater to the one of
|
||||
// the SCC of S2 (called the "threshold").
|
||||
int threshold = p.first->second;
|
||||
std::list<const state*> rem;
|
||||
while (threshold < ecs_->root.top().index)
|
||||
{
|
||||
assert(!ecs_->root.empty());
|
||||
assert(!arc.empty());
|
||||
acc |= ecs_->root.top().condition;
|
||||
acc |= arc.top();
|
||||
rem.splice(rem.end(), ecs_->root.rem());
|
||||
ecs_->root.pop();
|
||||
arc.pop();
|
||||
}
|
||||
// Note that we do not always have
|
||||
// threshold == ecs_->root.top().index
|
||||
// after this loop, the SCC whose index is threshold might have
|
||||
// been merged with a lower SCC.
|
||||
|
||||
// Accumulate all acceptance conditions into the merged SCC.
|
||||
ecs_->root.top().condition |= acc;
|
||||
ecs_->root.rem().splice(ecs_->root.rem().end(), rem);
|
||||
|
||||
if (ecs_->aut->acc().accepting(ecs_->root.top().condition))
|
||||
{
|
||||
// We have found an accepting SCC.
|
||||
// Release all iterators in TODO.
|
||||
while (!todo.empty())
|
||||
{
|
||||
ecs_->aut->release_iter(todo.top().second);
|
||||
todo.pop();
|
||||
dec_depth();
|
||||
}
|
||||
// Use this state to start the computation of an accepting
|
||||
// cycle.
|
||||
ecs_->cycle_seed = p.first->first;
|
||||
set_states(ecs_->states());
|
||||
return std::make_shared<couvreur99_check_result>(ecs_, options());
|
||||
}
|
||||
}
|
||||
// This automaton recognizes no word.
|
||||
set_states(ecs_->states());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<const couvreur99_check_status>
|
||||
couvreur99_check::result() const
|
||||
{
|
||||
return ecs_;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
couvreur99_check::print_stats(std::ostream& os) const
|
||||
{
|
||||
ecs_->print_stats(os);
|
||||
os << transitions() << " transitions explored" << std::endl;
|
||||
os << max_depth() << " items max in DFS search stack" << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
couvreur99_check_shy::todo_item::todo_item(const state* s, int n,
|
||||
couvreur99_check_shy* shy)
|
||||
: s(s), n(n)
|
||||
{
|
||||
for (auto iter: shy->ecs_->aut->succ(s))
|
||||
{
|
||||
q.emplace_back(iter->acc(),
|
||||
iter->dst());
|
||||
shy->inc_depth();
|
||||
shy->inc_transitions();
|
||||
}
|
||||
}
|
||||
|
||||
couvreur99_check_shy::couvreur99_check_shy(const const_twa_ptr& a,
|
||||
option_map o)
|
||||
: couvreur99_check(a, o), num(1)
|
||||
{
|
||||
group_ = o.get("group", 1);
|
||||
group2_ = o.get("group2", 0);
|
||||
group_ |= group2_;
|
||||
|
||||
// Setup depth-first search from the initial state.
|
||||
const state* i = ecs_->aut->get_init_state();
|
||||
ecs_->h[i] = ++num;
|
||||
ecs_->root.push(num);
|
||||
todo.emplace_back(i, num, this);
|
||||
inc_depth(1);
|
||||
}
|
||||
|
||||
couvreur99_check_shy::~couvreur99_check_shy()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
couvreur99_check_shy::clear_todo()
|
||||
{
|
||||
// We must destroy all states appearing in TODO
|
||||
// unless they are used as keys in H.
|
||||
while (!todo.empty())
|
||||
{
|
||||
succ_queue& queue = todo.back().q;
|
||||
for (auto& q: queue)
|
||||
{
|
||||
// Destroy the state if it is a clone of a state in the
|
||||
// heap or if it is an unknown state.
|
||||
auto i = ecs_->h.find(q.s);
|
||||
if (i == ecs_->h.end() || i->first != q.s)
|
||||
q.s->destroy();
|
||||
}
|
||||
dec_depth(todo.back().q.size() + 1);
|
||||
todo.pop_back();
|
||||
}
|
||||
dec_depth(ecs_->root.clear_rem());
|
||||
assert(depth() == 0);
|
||||
}
|
||||
|
||||
void
|
||||
couvreur99_check_shy::dump_queue(std::ostream& os)
|
||||
{
|
||||
os << "--- TODO ---\n";
|
||||
unsigned pos = 0;
|
||||
for (auto& ti: todo)
|
||||
{
|
||||
++pos;
|
||||
os << '#' << pos << " s:" << ti.s << " n:" << ti.n
|
||||
<< " q:{";
|
||||
for (auto qi = ti.q.begin(); qi != ti.q.end();)
|
||||
{
|
||||
os << qi->s;
|
||||
++qi;
|
||||
if (qi != ti.q.end())
|
||||
os << ", ";
|
||||
}
|
||||
os << "}\n";
|
||||
}
|
||||
}
|
||||
|
||||
emptiness_check_result_ptr
|
||||
couvreur99_check_shy::check()
|
||||
{
|
||||
{
|
||||
auto acc = ecs_->aut->acc();
|
||||
if (acc.get_acceptance().is_ff())
|
||||
return nullptr;
|
||||
if (acc.uses_fin_acceptance())
|
||||
throw std::runtime_error
|
||||
("Fin acceptance is not supported by couvreur99()");
|
||||
}
|
||||
// Position in the loop seeking known successors.
|
||||
pos = todo.back().q.begin();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
#ifdef TRACE
|
||||
dump_queue();
|
||||
#endif
|
||||
|
||||
assert(ecs_->root.size() == 1 + arc.size());
|
||||
|
||||
// Get the successors of the current state.
|
||||
succ_queue& queue = todo.back().q;
|
||||
|
||||
// If there is no more successor, backtrack.
|
||||
if (queue.empty())
|
||||
{
|
||||
trace << "backtrack" << std::endl;
|
||||
|
||||
// We have explored all successors of state CURR.
|
||||
const state* curr = todo.back().s;
|
||||
int index = todo.back().n;
|
||||
|
||||
// Backtrack TODO.
|
||||
todo.pop_back();
|
||||
dec_depth();
|
||||
|
||||
if (todo.empty())
|
||||
{
|
||||
// This automaton recognizes no word.
|
||||
set_states(ecs_->states());
|
||||
assert(poprem_ || depth() == 0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pos = todo.back().q.begin();
|
||||
|
||||
// If poprem is used, fill rem with any component removed,
|
||||
// so that remove_component() does not have to traverse
|
||||
// the SCC again.
|
||||
if (poprem_)
|
||||
{
|
||||
auto i = ecs_->h.find(curr);
|
||||
assert(i != ecs_->h.end());
|
||||
assert(i->first == curr);
|
||||
ecs_->root.rem().push_front(i->first);
|
||||
inc_depth();
|
||||
}
|
||||
|
||||
// When backtracking the root of an SCC, we must also
|
||||
// remove that SCC from the ARC/ROOT stacks. We must
|
||||
// discard from H all reachable states from this SCC.
|
||||
assert(!ecs_->root.empty());
|
||||
if (ecs_->root.top().index == index)
|
||||
{
|
||||
assert(!arc.empty());
|
||||
arc.pop();
|
||||
remove_component(curr);
|
||||
ecs_->root.pop();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// We always make a first pass over the successors of a state
|
||||
// to check whether it contains some state we have already seen.
|
||||
// This way we hope to merge the most SCCs before stacking new
|
||||
// states.
|
||||
//
|
||||
// So are we checking for known states ? If yes, POS tells us
|
||||
// which state we are considering. Otherwise just pick the
|
||||
// first one.
|
||||
succ_queue::iterator old;
|
||||
if (pos == queue.end())
|
||||
old = queue.begin();
|
||||
else
|
||||
old = pos;
|
||||
if (pos != queue.end())
|
||||
++pos;
|
||||
//int* i = sip.second;
|
||||
|
||||
successor succ = *old;
|
||||
trace << "picked state " << succ.s << '\n';
|
||||
auto i = ecs_->h.find(succ.s);
|
||||
|
||||
if (i == ecs_->h.end())
|
||||
{
|
||||
// It's a new state.
|
||||
// If we are seeking known states, just skip it.
|
||||
if (pos != queue.end())
|
||||
continue;
|
||||
|
||||
trace << "new state\n";
|
||||
|
||||
// Otherwise, number it and stack it so we recurse.
|
||||
queue.erase(old);
|
||||
dec_depth();
|
||||
ecs_->h[succ.s] = ++num;
|
||||
ecs_->root.push(num);
|
||||
arc.push(succ.acc);
|
||||
todo.emplace_back(succ.s, num, this);
|
||||
pos = todo.back().q.begin();
|
||||
inc_depth();
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's an known state. Use i->first from now on.
|
||||
succ.s->destroy();
|
||||
|
||||
queue.erase(old);
|
||||
dec_depth();
|
||||
|
||||
// Skip dead states.
|
||||
if (i->second == -1)
|
||||
{
|
||||
trace << "dead state\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
trace << "merging...\n";
|
||||
|
||||
// Now this is the most interesting case. We have
|
||||
// reached a state S1 which is already part of a
|
||||
// non-dead SCC. Any such non-dead SCC has
|
||||
// necessarily been crossed by our path to this
|
||||
// state: there is a state S2 in our path which
|
||||
// belongs to this SCC too. We are going to merge
|
||||
// all states between this S1 and S2 into this
|
||||
// SCC.
|
||||
//
|
||||
// This merge is easy to do because the order of
|
||||
// the SCC in ROOT is ascending: we just have to
|
||||
// merge all SCCs from the top of ROOT that have
|
||||
// an index greater to the one of the SCC of S2
|
||||
// (called the "threshold").
|
||||
int threshold = i->second;
|
||||
std::list<const state*> rem;
|
||||
acc_cond::mark_t acc = succ.acc;
|
||||
while (threshold < ecs_->root.top().index)
|
||||
{
|
||||
assert(!ecs_->root.empty());
|
||||
assert(!arc.empty());
|
||||
acc |= ecs_->root.top().condition;
|
||||
acc |= arc.top();
|
||||
rem.splice(rem.end(), ecs_->root.rem());
|
||||
ecs_->root.pop();
|
||||
arc.pop();
|
||||
}
|
||||
// Note that we do not always have
|
||||
// threshold == ecs_->root.top().index
|
||||
// after this loop, the SCC whose index is threshold
|
||||
// might have been merged with a lower SCC.
|
||||
|
||||
// Accumulate all acceptance conditions into the
|
||||
// merged SCC.
|
||||
ecs_->root.top().condition |= acc;
|
||||
ecs_->root.rem().splice(ecs_->root.rem().end(), rem);
|
||||
|
||||
// Have we found all acceptance conditions?
|
||||
if (ecs_->aut->acc().accepting(ecs_->root.top().condition))
|
||||
{
|
||||
// Use this state to start the computation of an accepting
|
||||
// cycle.
|
||||
ecs_->cycle_seed = i->first;
|
||||
|
||||
// We have found an accepting SCC. Clean up TODO.
|
||||
clear_todo();
|
||||
set_states(ecs_->states());
|
||||
return std::make_shared<couvreur99_check_result>(ecs_, options());
|
||||
}
|
||||
// Group the pending successors of formed SCC if requested.
|
||||
if (group_)
|
||||
{
|
||||
assert(todo.back().s);
|
||||
while (ecs_->root.top().index < todo.back().n)
|
||||
{
|
||||
todo_list::reverse_iterator prev = todo.rbegin();
|
||||
todo_list::reverse_iterator last = prev++;
|
||||
// If group2 is used we insert the last->q in front
|
||||
// of prev->q so that the states in prev->q are checked
|
||||
// for existence again after we have processed the states
|
||||
// of last->q. Otherwise we just append to the end.
|
||||
prev->q.splice(group2_ ? prev->q.begin() : prev->q.end(),
|
||||
last->q);
|
||||
|
||||
if (poprem_)
|
||||
{
|
||||
const state* s = todo.back().s;
|
||||
auto i = ecs_->h.find(s);
|
||||
assert(i != ecs_->h.end());
|
||||
assert(i->first == s);
|
||||
ecs_->root.rem().push_front(i->first);
|
||||
// Don't change the stack depth, since
|
||||
// we are just moving the state from TODO to REM.
|
||||
}
|
||||
else
|
||||
{
|
||||
dec_depth();
|
||||
}
|
||||
todo.pop_back();
|
||||
}
|
||||
pos = todo.back().q.begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emptiness_check_ptr
|
||||
couvreur99(const const_twa_ptr& a, option_map o)
|
||||
{
|
||||
if (o.get("shy"))
|
||||
return std::make_shared<couvreur99_check_shy>(a, o);
|
||||
return std::make_shared<couvreur99_check>(a, o);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement 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 <stack>
|
||||
#include "status.hh"
|
||||
#include "twaalgos/emptiness.hh"
|
||||
#include "twaalgos/emptiness_stats.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup emptiness_check_algorithms
|
||||
/// @{
|
||||
|
||||
/// \brief Check whether the language of an automate is empty.
|
||||
///
|
||||
/// This is based on the following paper.
|
||||
/** \verbatim
|
||||
@InProceedings{couvreur.99.fm,
|
||||
author = {Jean-Michel Couvreur},
|
||||
title = {On-the-fly Verification of Temporal Logic},
|
||||
pages = {253--271},
|
||||
editor = {Jeannette M. Wing and Jim Woodcock and Jim Davies},
|
||||
booktitle = {Proceedings of the World Congress on Formal Methods in
|
||||
the Development of Computing Systems (FM'99)},
|
||||
publisher = {Springer-Verlag},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
volume = {1708},
|
||||
year = {1999},
|
||||
address = {Toulouse, France},
|
||||
month = {September},
|
||||
isbn = {3-540-66587-0}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// A recursive definition of the algorithm would look as follows,
|
||||
/// but the implementation is of course not recursive.
|
||||
/// (<code><Sigma, Q, delta, q, F></code> is the automaton to
|
||||
/// check, H is an associative array mapping each state to its
|
||||
/// positive DFS order or 0 if it is dead, SCC is and ACC are two
|
||||
/// stacks.)
|
||||
///
|
||||
/** \verbatim
|
||||
check(<Sigma, Q, delta, q, F>, H, SCC, ACC)
|
||||
if q is not in H // new state
|
||||
H[q] = H.size + 1
|
||||
SCC.push(<H[q], {}>)
|
||||
forall <a, s> : <q, _, a, s> in delta
|
||||
ACC.push(a)
|
||||
res = check(<Sigma, Q, delta, s, F>, H, SCC, ACC)
|
||||
if res
|
||||
return res
|
||||
<n, _> = SCC.top()
|
||||
if n = H[q]
|
||||
SCC.pop()
|
||||
mark_reachable_states_as_dead(<Sigma, Q, delta, q, F>, H$)
|
||||
return 0
|
||||
else
|
||||
if H[q] = 0 // dead state
|
||||
ACC.pop()
|
||||
return true
|
||||
else // state in stack: merge SCC
|
||||
all = {}
|
||||
do
|
||||
<n, a> = SCC.pop()
|
||||
all = all union a union { ACC.pop() }
|
||||
until n <= H[q]
|
||||
SCC.push(<n, all>)
|
||||
if all != F
|
||||
return 0
|
||||
return new emptiness_check_result(necessary data)
|
||||
\endverbatim */
|
||||
///
|
||||
/// check() returns 0 iff the automaton's language is empty. It
|
||||
/// returns an instance of emptiness_check_result. If the automaton
|
||||
/// accept a word. (Use emptiness_check_result::accepting_run() to
|
||||
/// extract an accepting run.)
|
||||
///
|
||||
/// There are two variants of this algorithm: spot::couvreur99_check and
|
||||
/// spot::couvreur99_check_shy. They differ in their memory usage, the
|
||||
/// number for successors computed before they are used and the way
|
||||
/// the depth first search is directed.
|
||||
///
|
||||
/// spot::couvreur99_check performs a straightforward depth first search.
|
||||
/// The DFS stacks store twa_succ_iterators, so that only the
|
||||
/// iterators which really are explored are computed.
|
||||
///
|
||||
/// spot::couvreur99_check_shy tries to explore successors which are
|
||||
/// visited states first. this helps to merge SCCs and generally
|
||||
/// helps to produce shorter counter-examples. However this
|
||||
/// algorithm cannot stores unprocessed successors as
|
||||
/// twa_succ_iterators: it must compute all successors of a state
|
||||
/// at once in order to decide which to explore first, and must keep
|
||||
/// a list of all unexplored successors in its DFS stack.
|
||||
///
|
||||
/// The couvreur99() function is a wrapper around these two flavors
|
||||
/// of the algorithm. \a options is an option map that specifies
|
||||
/// which algorithms should be used, and how.
|
||||
///
|
||||
/// The following options are available.
|
||||
/// \li \c "shy" : if non zero, then spot::couvreur99_check_shy is used,
|
||||
/// otherwise (and by default) spot::couvreur99_check is used.
|
||||
///
|
||||
/// \li \c "poprem" : specifies how the algorithm should handle the
|
||||
/// destruction of non-accepting maximal strongly connected
|
||||
/// components. If \c poprem is non null, the algorithm will keep a
|
||||
/// list of all states of a SCC that are fully processed and should
|
||||
/// be removed once the MSCC is popped. If \c poprem is null (the
|
||||
/// default), the MSCC will be traversed again (i.e. generating the
|
||||
/// successors of the root recursively) for deletion. This is a
|
||||
/// choice between memory and speed.
|
||||
///
|
||||
/// \li \c "group" : this options is used only by spot::couvreur99_check_shy.
|
||||
/// If non null (the default), the successors of all the
|
||||
/// states that belong to the same SCC will be considered when
|
||||
/// choosing a successor. Otherwise, only the successor of the
|
||||
/// topmost state on the DFS stack are considered.
|
||||
SPOT_API emptiness_check_ptr
|
||||
couvreur99(const const_twa_ptr& a, option_map options = option_map());
|
||||
|
||||
#ifndef SWIG
|
||||
/// \brief An implementation of the Couvreur99 emptiness-check algorithm.
|
||||
///
|
||||
/// See the documentation for spot::couvreur99.
|
||||
class SPOT_API couvreur99_check: public emptiness_check, public ec_statistics
|
||||
{
|
||||
public:
|
||||
couvreur99_check(const const_twa_ptr& a, option_map o = option_map());
|
||||
virtual ~couvreur99_check();
|
||||
|
||||
/// Check whether the automaton's language is empty.
|
||||
virtual emptiness_check_result_ptr check();
|
||||
|
||||
virtual std::ostream& print_stats(std::ostream& os) const;
|
||||
|
||||
/// \brief Return the status of the emptiness-check.
|
||||
///
|
||||
/// When check() succeed, the status should be passed along
|
||||
/// to spot::counter_example.
|
||||
///
|
||||
/// This status should not be deleted, it is a pointer
|
||||
/// to a member of this class that will be deleted when
|
||||
/// the couvreur99 object is deleted.
|
||||
std::shared_ptr<const couvreur99_check_status> result() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<couvreur99_check_status> ecs_;
|
||||
/// \brief Remove a strongly component from the hash.
|
||||
///
|
||||
/// This function remove all accessible state from a given
|
||||
/// state. In other words, it removes the strongly connected
|
||||
/// component that contains this state.
|
||||
void remove_component(const state* start_delete);
|
||||
|
||||
/// Whether to store the state to be removed.
|
||||
bool poprem_;
|
||||
/// Number of dead SCC removed by the algorithm.
|
||||
unsigned removed_components;
|
||||
unsigned get_removed_components() const;
|
||||
unsigned get_vmsize() const;
|
||||
};
|
||||
|
||||
/// \brief A version of spot::couvreur99_check that tries to visit
|
||||
/// known states first.
|
||||
///
|
||||
/// See the documentation for spot::couvreur99.
|
||||
class SPOT_API couvreur99_check_shy final: public couvreur99_check
|
||||
{
|
||||
public:
|
||||
couvreur99_check_shy(const const_twa_ptr& a, option_map o = option_map());
|
||||
virtual ~couvreur99_check_shy();
|
||||
|
||||
virtual emptiness_check_result_ptr check();
|
||||
|
||||
protected:
|
||||
struct successor {
|
||||
acc_cond::mark_t acc;
|
||||
const spot::state* s;
|
||||
successor(acc_cond::mark_t acc, const spot::state* s): acc(acc), s(s) {}
|
||||
};
|
||||
|
||||
// We use five main data in this algorithm:
|
||||
// * couvreur99_check::root, a stack of strongly connected components (SCC),
|
||||
// * couvreur99_check::h, a hash of all visited nodes, with their order,
|
||||
// (it is called "Hash" in Couvreur's paper)
|
||||
// * arc, a stack of acceptance conditions between each of these SCC,
|
||||
std::stack<acc_cond::mark_t> arc;
|
||||
// * num, the number of visited nodes. Used to set the order of each
|
||||
// visited node,
|
||||
int num;
|
||||
// * todo, the depth-first search stack. This holds pairs of the
|
||||
// form (STATE, SUCCESSORS) where SUCCESSORS is a list of
|
||||
// (ACCEPTANCE_CONDITIONS, STATE) pairs.
|
||||
typedef std::list<successor> succ_queue;
|
||||
|
||||
// Position in the loop seeking known successors.
|
||||
succ_queue::iterator pos;
|
||||
|
||||
struct todo_item
|
||||
{
|
||||
const state* s;
|
||||
int n;
|
||||
succ_queue q; // Unprocessed successors of S
|
||||
todo_item(const state* s, int n, couvreur99_check_shy* shy);
|
||||
};
|
||||
|
||||
typedef std::list<todo_item> todo_list;
|
||||
todo_list todo;
|
||||
|
||||
void clear_todo();
|
||||
|
||||
/// Dump the queue for debugging.
|
||||
void dump_queue(std::ostream& os = std::cerr);
|
||||
|
||||
/// Whether successors should be grouped for states in the same SCC.
|
||||
bool group_;
|
||||
// If the "group2" option is set (it implies "group"), we
|
||||
// reprocess the successor states of SCC that have been merged.
|
||||
bool group2_;
|
||||
};
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2014 Laboratoire de Recherche et Developpement de
|
||||
// l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 "sccstack.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
scc_stack::connected_component::connected_component(int i)
|
||||
{
|
||||
index = i;
|
||||
condition = 0U;
|
||||
}
|
||||
|
||||
scc_stack::connected_component&
|
||||
scc_stack::top()
|
||||
{
|
||||
return s.front();
|
||||
}
|
||||
|
||||
const scc_stack::connected_component&
|
||||
scc_stack::top() const
|
||||
{
|
||||
return s.front();
|
||||
}
|
||||
|
||||
void
|
||||
scc_stack::pop()
|
||||
{
|
||||
// assert(rem().empty());
|
||||
s.pop_front();
|
||||
}
|
||||
|
||||
void
|
||||
scc_stack::push(int index)
|
||||
{
|
||||
s.emplace_front(index);
|
||||
}
|
||||
|
||||
std::list<const state*>&
|
||||
scc_stack::rem()
|
||||
{
|
||||
return top().rem;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
scc_stack::size() const
|
||||
{
|
||||
return s.size();
|
||||
}
|
||||
|
||||
bool
|
||||
scc_stack::empty() const
|
||||
{
|
||||
return s.empty();
|
||||
}
|
||||
|
||||
unsigned
|
||||
scc_stack::clear_rem()
|
||||
{
|
||||
unsigned n = 0;
|
||||
for (stack_type::iterator i = s.begin(); i != s.end(); ++i)
|
||||
{
|
||||
n += i->rem.size();
|
||||
i->rem.clear();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014 Laboratoire de Recherche et Développement de
|
||||
// l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 <bddx.h>
|
||||
#include <list>
|
||||
#include "twa/twa.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
// A stack of Strongly-Connected Components, as needed by the
|
||||
// Tarjan-Couvreur algorithm.
|
||||
class SPOT_API scc_stack
|
||||
{
|
||||
public:
|
||||
struct connected_component
|
||||
{
|
||||
public:
|
||||
connected_component(int index = -1);
|
||||
|
||||
/// Index of the SCC.
|
||||
int index;
|
||||
/// The union of all acceptance marks of transitions the
|
||||
/// connected component.
|
||||
acc_cond::mark_t condition;
|
||||
|
||||
std::list<const state*> rem;
|
||||
};
|
||||
|
||||
/// Stack a new SCC with index \a index.
|
||||
void push(int index);
|
||||
|
||||
/// Access the top SCC.
|
||||
connected_component& top();
|
||||
|
||||
/// Access the top SCC.
|
||||
const connected_component& top() const;
|
||||
|
||||
/// Pop the top SCC.
|
||||
void pop();
|
||||
|
||||
/// How many SCC are in stack.
|
||||
size_t size() const;
|
||||
|
||||
/// The \c rem member of the top SCC.
|
||||
std::list<const state*>& rem();
|
||||
|
||||
/// Purge all \c rem members.
|
||||
///
|
||||
/// \return the number of elements cleared.
|
||||
unsigned clear_rem();
|
||||
|
||||
/// Is the stack empty?
|
||||
bool empty() const;
|
||||
|
||||
typedef std::list<connected_component> stack_type;
|
||||
stack_type s;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2014 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 <ostream>
|
||||
#include "status.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
couvreur99_check_status::couvreur99_check_status(const const_twa_ptr& aut)
|
||||
: aut(aut)
|
||||
{
|
||||
}
|
||||
|
||||
couvreur99_check_status::~couvreur99_check_status()
|
||||
{
|
||||
hash_type::iterator i = h.begin();
|
||||
while (i != h.end())
|
||||
{
|
||||
// Advance the iterator before deleting the key.
|
||||
const state* s = i->first;
|
||||
++i;
|
||||
s->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
couvreur99_check_status::print_stats(std::ostream& os) const
|
||||
{
|
||||
os << h.size() << " unique states visited" << std::endl;
|
||||
os << root.size()
|
||||
<< " strongly connected components in search stack\n";
|
||||
}
|
||||
|
||||
int
|
||||
couvreur99_check_status::states() const
|
||||
{
|
||||
return h.size();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sccstack.hh"
|
||||
#include "twa/twa.hh"
|
||||
#include <iosfwd>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief The status of the emptiness-check on success.
|
||||
///
|
||||
/// This contains everything needed to construct a counter-example:
|
||||
/// the automata, the stack of SCCs traversed by the counter-example,
|
||||
/// and the heap of visited states with their indexes.
|
||||
class SPOT_API couvreur99_check_status
|
||||
{
|
||||
public:
|
||||
couvreur99_check_status(const const_twa_ptr& aut);
|
||||
|
||||
~couvreur99_check_status();
|
||||
|
||||
const_twa_ptr aut;
|
||||
scc_stack root;
|
||||
|
||||
typedef std::unordered_map<const state*, int,
|
||||
state_ptr_hash, state_ptr_equal> hash_type;
|
||||
hash_type h;
|
||||
|
||||
const state* cycle_seed;
|
||||
|
||||
/// Output statistics about this object.
|
||||
void print_stats(std::ostream& os) const;
|
||||
|
||||
/// Return the number of states visited by the search
|
||||
int states() const;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,414 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2010, 2011, 2013, 2014, 2015 Laboratoire de
|
||||
// recherche et développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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/>.
|
||||
|
||||
//#define TRACE
|
||||
|
||||
#include <iostream>
|
||||
#ifdef TRACE
|
||||
#define trace std::cerr
|
||||
#else
|
||||
#define trace while (0) std::cerr
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "twa/twa.hh"
|
||||
#include "misc/hash.hh"
|
||||
#include "emptiness.hh"
|
||||
#include "emptiness_stats.hh"
|
||||
#include "gv04.hh"
|
||||
#include "bfssteps.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct stack_entry
|
||||
{
|
||||
const state* s; // State stored in stack entry.
|
||||
twa_succ_iterator* lasttr; // Last transition explored from this state.
|
||||
int lowlink; // Lowlink value if this entry.
|
||||
int pre; // DFS predecessor.
|
||||
int acc; // Accepting state link.
|
||||
};
|
||||
|
||||
struct gv04: public emptiness_check, public ec_statistics
|
||||
{
|
||||
// Map of visited states.
|
||||
typedef std::unordered_map<const state*, size_t,
|
||||
state_ptr_hash, state_ptr_equal> hash_type;
|
||||
hash_type h;
|
||||
|
||||
// Stack of visited states on the path.
|
||||
typedef std::vector<stack_entry> stack_type;
|
||||
stack_type stack;
|
||||
|
||||
int top; // Top of SCC stack.
|
||||
int dftop; // Top of DFS stack.
|
||||
bool violation; // Whether an accepting run was found.
|
||||
|
||||
gv04(const const_twa_ptr& a, option_map o)
|
||||
: emptiness_check(a, o)
|
||||
{
|
||||
assert(a->num_sets() <= 1);
|
||||
}
|
||||
|
||||
~gv04()
|
||||
{
|
||||
for (stack_type::iterator i = stack.begin(); i != stack.end(); ++i)
|
||||
a_->release_iter(i->lasttr);
|
||||
hash_type::const_iterator s = h.begin();
|
||||
while (s != h.end())
|
||||
{
|
||||
// Advance the iterator before deleting the "key" pointer.
|
||||
const state* ptr = s->first;
|
||||
++s;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
virtual emptiness_check_result_ptr
|
||||
check()
|
||||
{
|
||||
top = dftop = -1;
|
||||
violation = false;
|
||||
push(a_->get_init_state(), false);
|
||||
|
||||
while (!violation && dftop >= 0)
|
||||
{
|
||||
trace << "Main iteration (top = " << top
|
||||
<< ", dftop = " << dftop
|
||||
<< ", s = " << a_->format_state(stack[dftop].s)
|
||||
<< ')' << std::endl;
|
||||
|
||||
twa_succ_iterator* iter = stack[dftop].lasttr;
|
||||
bool cont;
|
||||
if (!iter)
|
||||
{
|
||||
iter = stack[dftop].lasttr = a_->succ_iter(stack[dftop].s);
|
||||
cont = iter->first();
|
||||
}
|
||||
else
|
||||
{
|
||||
cont = iter->next();
|
||||
}
|
||||
|
||||
if (!cont)
|
||||
{
|
||||
trace << " No more successors" << std::endl;
|
||||
pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
const state* s_prime = iter->dst();
|
||||
bool acc =
|
||||
a_->acc().accepting(iter->acc());
|
||||
inc_transitions();
|
||||
|
||||
trace << " Next successor: s_prime = "
|
||||
<< a_->format_state(s_prime)
|
||||
<< (acc ? " (with accepting link)" : "");
|
||||
|
||||
hash_type::const_iterator i = h.find(s_prime);
|
||||
|
||||
if (i == h.end())
|
||||
{
|
||||
trace << " is a new state." << std::endl;
|
||||
push(s_prime, acc);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i->second < stack.size()
|
||||
&& stack[i->second].s->compare(s_prime) == 0)
|
||||
{
|
||||
// s_prime has a clone on stack
|
||||
trace << " is on stack." << std::endl;
|
||||
// This is an addition to GV04 to support TBA.
|
||||
violation |= acc;
|
||||
lowlinkupdate(dftop, i->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << " has been seen, but is no longer on stack."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
s_prime->destroy();
|
||||
}
|
||||
}
|
||||
set_states(h.size());
|
||||
}
|
||||
if (violation)
|
||||
return std::make_shared<result>(*this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
push(const state* s, bool accepting)
|
||||
{
|
||||
trace << " push(s = " << a_->format_state(s)
|
||||
<< ", accepting = " << accepting << ")\n";
|
||||
|
||||
h[s] = ++top;
|
||||
|
||||
stack_entry ss = { s, nullptr, top, dftop, 0 };
|
||||
|
||||
if (accepting)
|
||||
ss.acc = top - 1; // This differs from GV04 to support TBA.
|
||||
else if (dftop >= 0)
|
||||
ss.acc = stack[dftop].acc;
|
||||
else
|
||||
ss.acc = -1;
|
||||
|
||||
trace << " s.lowlink = " << top << std::endl
|
||||
<< " s.acc = " << ss.acc << std::endl;
|
||||
|
||||
stack.push_back(ss);
|
||||
dftop = top;
|
||||
inc_depth();
|
||||
}
|
||||
|
||||
void
|
||||
pop()
|
||||
{
|
||||
trace << " pop()\n";
|
||||
|
||||
int p = stack[dftop].pre;
|
||||
if (p >= 0)
|
||||
lowlinkupdate(p, dftop);
|
||||
if (stack[dftop].lowlink == dftop)
|
||||
{
|
||||
assert(static_cast<unsigned int>(top + 1) == stack.size());
|
||||
for (int i = top; i >= dftop; --i)
|
||||
{
|
||||
a_->release_iter(stack[i].lasttr);
|
||||
stack.pop_back();
|
||||
dec_depth();
|
||||
}
|
||||
top = dftop - 1;
|
||||
}
|
||||
dftop = p;
|
||||
}
|
||||
|
||||
void
|
||||
lowlinkupdate(int f, int t)
|
||||
{
|
||||
trace << " lowlinkupdate(f = " << f << ", t = " << t
|
||||
<< ")\n t.lowlink = " << stack[t].lowlink
|
||||
<< "\n f.lowlink = " << stack[f].lowlink
|
||||
<< "\n f.acc = " << stack[f].acc << '\n';
|
||||
int stack_t_lowlink = stack[t].lowlink;
|
||||
if (stack_t_lowlink <= stack[f].lowlink)
|
||||
{
|
||||
if (stack_t_lowlink <= stack[f].acc)
|
||||
violation = true;
|
||||
stack[f].lowlink = stack_t_lowlink;
|
||||
trace << " f.lowlink updated to "
|
||||
<< stack[f].lowlink << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::ostream&
|
||||
print_stats(std::ostream& os) const
|
||||
{
|
||||
os << h.size() << " unique states visited\n";
|
||||
os << transitions() << " transitions explored\n";
|
||||
os << max_depth() << " items max on stack\n";
|
||||
return os;
|
||||
}
|
||||
|
||||
struct result:
|
||||
public emptiness_check_result,
|
||||
public acss_statistics
|
||||
{
|
||||
gv04& data;
|
||||
|
||||
result(gv04& data)
|
||||
: emptiness_check_result(data.automaton(), data.options()),
|
||||
data(data)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
update_lowlinks()
|
||||
{
|
||||
// Transitively update the lowlinks, so we can use them in
|
||||
// to check SCC inclusion
|
||||
for (int i = 0; i <= data.top; ++i)
|
||||
{
|
||||
int l = data.stack[i].lowlink;
|
||||
if (l < i)
|
||||
{
|
||||
int ll = data.stack[i].lowlink = data.stack[l].lowlink;
|
||||
for (int j = i - 1; data.stack[j].lowlink != ll; --j)
|
||||
data.stack[j].lowlink = ll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual unsigned
|
||||
acss_states() const
|
||||
{
|
||||
// Gross!
|
||||
const_cast<result*>(this)->update_lowlinks();
|
||||
|
||||
int scc = data.stack[data.dftop].lowlink;
|
||||
int j = data.dftop;
|
||||
int s = 0;
|
||||
while (j >= 0 && data.stack[j].lowlink == scc)
|
||||
{
|
||||
--j;
|
||||
++s;
|
||||
}
|
||||
assert(s > 0);
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual twa_run_ptr
|
||||
accepting_run()
|
||||
{
|
||||
auto res = std::make_shared<twa_run>(automaton());
|
||||
|
||||
update_lowlinks();
|
||||
#ifdef TRACE
|
||||
for (int i = 0; i <= data.top; ++i)
|
||||
{
|
||||
trace << "state " << i << " ("
|
||||
<< data.a_->format_state(data.stack[i].s)
|
||||
<< ") has lowlink = " << data.stack[i].lowlink << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
// We will use the root of the last SCC as the start of the
|
||||
// cycle.
|
||||
int scc_root = data.stack[data.dftop].lowlink;
|
||||
assert(scc_root >= 0);
|
||||
|
||||
// Construct the prefix by unwinding the DFS stack before
|
||||
// scc_root.
|
||||
int father = data.stack[scc_root].pre;
|
||||
while (father >= 0)
|
||||
{
|
||||
twa_run::step st =
|
||||
{
|
||||
data.stack[father].s->clone(),
|
||||
data.stack[father].lasttr->cond(),
|
||||
data.stack[father].lasttr->acc()
|
||||
};
|
||||
res->prefix.push_front(st);
|
||||
father = data.stack[father].pre;
|
||||
}
|
||||
|
||||
// Construct the cycle in two phases. A first BFS finds the
|
||||
// shortest path from scc_root to an accepting transition.
|
||||
// A second BFS then search a path back to scc_root. If
|
||||
// there is no acceptance conditions we just use the second
|
||||
// BFS to find a cycle around scc_root.
|
||||
|
||||
struct first_bfs: bfs_steps
|
||||
{
|
||||
const gv04& data;
|
||||
int scc_root;
|
||||
result* r;
|
||||
|
||||
first_bfs(result* r, int scc_root)
|
||||
: bfs_steps(r->data.automaton()), data(r->data),
|
||||
scc_root(scc_root), r(r)
|
||||
{
|
||||
}
|
||||
|
||||
virtual const state*
|
||||
filter(const state* s)
|
||||
{
|
||||
// Do not escape the SCC
|
||||
hash_type::const_iterator j = data.h.find(s);
|
||||
if (// This state was never visited so far.
|
||||
j == data.h.end()
|
||||
// Or it was discarded
|
||||
|| j->second >= data.stack.size()
|
||||
// Or it was discarded (but its stack slot reused)
|
||||
|| data.stack[j->second].s->compare(s)
|
||||
// Or it is still on the stack but not in the SCC
|
||||
|| data.stack[j->second].lowlink < scc_root)
|
||||
{
|
||||
s->destroy();
|
||||
return nullptr;
|
||||
}
|
||||
r->inc_ars_cycle_states();
|
||||
s->destroy();
|
||||
return j->first;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
match(twa_run::step& step, const state*)
|
||||
{
|
||||
return step.acc != 0U;
|
||||
}
|
||||
};
|
||||
|
||||
struct second_bfs: first_bfs
|
||||
{
|
||||
const state* target;
|
||||
second_bfs(result* r, int scc_root, const state* target)
|
||||
: first_bfs(r, scc_root), target(target)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool
|
||||
match(twa_run::step&, const state* s)
|
||||
{
|
||||
return s == target;
|
||||
}
|
||||
};
|
||||
|
||||
const state* bfs_start = data.stack[scc_root].s;
|
||||
const state* bfs_end = bfs_start;
|
||||
if (a_->num_sets() > 0)
|
||||
{
|
||||
first_bfs b1(this, scc_root);
|
||||
bfs_start = b1.search(bfs_start, res->cycle);
|
||||
assert(bfs_start);
|
||||
}
|
||||
if (bfs_start != bfs_end || res->cycle.empty())
|
||||
{
|
||||
second_bfs b2(this, scc_root, bfs_end);
|
||||
bfs_start = b2.search(bfs_start, res->cycle);
|
||||
assert(bfs_start == bfs_end);
|
||||
}
|
||||
|
||||
assert(res->cycle.begin() != res->cycle.end());
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
emptiness_check_ptr
|
||||
explicit_gv04_check(const const_twa_ptr& a, option_map o)
|
||||
{
|
||||
return std::make_shared<gv04>(a, o);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014 Laboratoire de Recherche et Developpement
|
||||
// de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 "misc/optionmap.hh"
|
||||
#include "twa/fwd.hh"
|
||||
#include "emptiness.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Emptiness check based on Geldenhuys and Valmari's
|
||||
/// TACAS'04 paper.
|
||||
/// \ingroup emptiness_check_algorithms
|
||||
/// \pre The automaton \a a must have at most one acceptance condition.
|
||||
///
|
||||
/// The original algorithm, coming from the following paper, has only
|
||||
/// been slightly modified to work on transition-based automata.
|
||||
/** \verbatim
|
||||
@InProceedings{geldenhuys.04.tacas,
|
||||
author = {Jaco Geldenhuys and Antti Valmari},
|
||||
title = {Tarjan's Algorithm Makes On-the-Fly {LTL} Verification
|
||||
More Efficient},
|
||||
booktitle = {Proceedings of the 10th International Conference on Tools
|
||||
and Algorithms for the Construction and Analysis of Systems
|
||||
(TACAS'04)},
|
||||
editor = {Kurt Jensen and Andreas Podelski},
|
||||
pages = {205--219},
|
||||
year = {2004},
|
||||
publisher = {Springer-Verlag},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
volume = {2988},
|
||||
isbn = {3-540-21299-X}
|
||||
}
|
||||
\endverbatim */
|
||||
SPOT_API emptiness_check_ptr
|
||||
explicit_gv04_check(const const_twa_ptr& a, option_map o = option_map());
|
||||
}
|
||||
|
|
@ -1,592 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2012, 2014, 2015 Laboratoire de Recherche et
|
||||
// Developpement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 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 <ostream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include "twa/twa.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "hoa.hh"
|
||||
#include "reachiter.hh"
|
||||
#include "misc/escape.hh"
|
||||
#include "misc/bddlt.hh"
|
||||
#include "misc/minato.hh"
|
||||
#include "twa/formula2bdd.hh"
|
||||
#include "tl/formula.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct metadata final
|
||||
{
|
||||
// Assign a number to each atomic proposition.
|
||||
typedef std::map<int, unsigned> ap_map;
|
||||
ap_map ap;
|
||||
typedef std::vector<int> vap_t;
|
||||
vap_t vap;
|
||||
|
||||
std::vector<bool> common_acc;
|
||||
bool has_state_acc;
|
||||
bool is_complete;
|
||||
bool is_deterministic;
|
||||
bool is_colored;
|
||||
bool use_implicit_labels;
|
||||
bool use_state_labels = true;
|
||||
bdd all_ap;
|
||||
|
||||
// Label support: the set of all conditions occurring in the
|
||||
// automaton.
|
||||
typedef std::map<bdd, std::string, bdd_less_than> sup_map;
|
||||
sup_map sup;
|
||||
|
||||
metadata(const const_twa_graph_ptr& aut, bool implicit,
|
||||
bool state_labels)
|
||||
{
|
||||
check_det_and_comp(aut);
|
||||
use_implicit_labels = implicit && is_deterministic && is_complete;
|
||||
use_state_labels &= state_labels;
|
||||
number_all_ap();
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
emit_acc(std::ostream& os,
|
||||
const const_twa_graph_ptr& aut,
|
||||
acc_cond::mark_t b)
|
||||
{
|
||||
// FIXME: We could use a cache for this.
|
||||
if (b == 0U)
|
||||
return os;
|
||||
os << " {";
|
||||
bool notfirst = false;
|
||||
for (auto v: aut->acc().sets(b))
|
||||
{
|
||||
if (notfirst)
|
||||
os << ' ';
|
||||
else
|
||||
notfirst = true;
|
||||
os << v;
|
||||
}
|
||||
os << '}';
|
||||
return os;
|
||||
}
|
||||
|
||||
void check_det_and_comp(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
std::string empty;
|
||||
|
||||
unsigned ns = aut->num_states();
|
||||
bool deterministic = true;
|
||||
bool complete = true;
|
||||
bool state_acc = true;
|
||||
bool nodeadend = true;
|
||||
bool colored = aut->num_sets() >= 1;
|
||||
for (unsigned src = 0; src < ns; ++src)
|
||||
{
|
||||
bdd sum = bddfalse;
|
||||
bdd available = bddtrue;
|
||||
bool st_acc = true;
|
||||
bool notfirst = false;
|
||||
acc_cond::mark_t prev = 0U;
|
||||
bool has_succ = false;
|
||||
bdd lastcond = bddfalse;
|
||||
for (auto& t: aut->out(src))
|
||||
{
|
||||
if (has_succ)
|
||||
use_state_labels &= lastcond == t.cond;
|
||||
else
|
||||
lastcond = t.cond;
|
||||
if (complete)
|
||||
sum |= t.cond;
|
||||
if (deterministic)
|
||||
{
|
||||
if (!bdd_implies(t.cond, available))
|
||||
deterministic = false;
|
||||
else
|
||||
available -= t.cond;
|
||||
}
|
||||
sup.insert(std::make_pair(t.cond, empty));
|
||||
if (st_acc)
|
||||
{
|
||||
if (notfirst && prev != t.acc)
|
||||
{
|
||||
st_acc = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
notfirst = true;
|
||||
prev = t.acc;
|
||||
}
|
||||
}
|
||||
if (colored)
|
||||
{
|
||||
auto a = t.acc;
|
||||
if (!a || a.remove_some(1))
|
||||
colored = false;
|
||||
}
|
||||
has_succ = true;
|
||||
}
|
||||
nodeadend &= has_succ;
|
||||
if (complete)
|
||||
complete &= sum == bddtrue;
|
||||
common_acc.push_back(st_acc);
|
||||
state_acc &= st_acc;
|
||||
}
|
||||
is_deterministic = deterministic;
|
||||
is_complete = complete;
|
||||
has_state_acc = state_acc;
|
||||
// If the automaton has state-based acceptance and contain
|
||||
// some states without successors do not declare it as
|
||||
// colored.
|
||||
is_colored = colored && (!has_state_acc || nodeadend);
|
||||
// If the automaton declares that it is deterministic or
|
||||
// state-based, make sure that it really is.
|
||||
assert(!aut->prop_deterministic() || deterministic);
|
||||
assert(!aut->prop_state_acc() || state_acc);
|
||||
}
|
||||
|
||||
void number_all_ap()
|
||||
{
|
||||
bdd all = bddtrue;
|
||||
for (auto& i: sup)
|
||||
all &= bdd_support(i.first);
|
||||
all_ap = all;
|
||||
|
||||
while (all != bddtrue)
|
||||
{
|
||||
int v = bdd_var(all);
|
||||
all = bdd_high(all);
|
||||
ap.insert(std::make_pair(v, vap.size()));
|
||||
vap.push_back(v);
|
||||
}
|
||||
|
||||
if (use_implicit_labels)
|
||||
return;
|
||||
|
||||
for (auto& i: sup)
|
||||
{
|
||||
bdd cond = i.first;
|
||||
if (cond == bddtrue)
|
||||
{
|
||||
i.second = "t";
|
||||
continue;
|
||||
}
|
||||
if (cond == bddfalse)
|
||||
{
|
||||
i.second = "f";
|
||||
continue;
|
||||
}
|
||||
std::ostringstream s;
|
||||
bool notfirstor = false;
|
||||
|
||||
minato_isop isop(cond);
|
||||
bdd cube;
|
||||
while ((cube = isop.next()) != bddfalse)
|
||||
{
|
||||
if (notfirstor)
|
||||
s << " | ";
|
||||
bool notfirstand = false;
|
||||
while (cube != bddtrue)
|
||||
{
|
||||
if (notfirstand)
|
||||
s << '&';
|
||||
else
|
||||
notfirstand = true;
|
||||
bdd h = bdd_high(cube);
|
||||
if (h == bddfalse)
|
||||
{
|
||||
s << '!' << ap[bdd_var(cube)];
|
||||
cube = bdd_low(cube);
|
||||
}
|
||||
else
|
||||
{
|
||||
s << ap[bdd_var(cube)];
|
||||
cube = h;
|
||||
}
|
||||
}
|
||||
notfirstor = true;
|
||||
}
|
||||
i.second = s.str();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
enum hoa_acceptance
|
||||
{
|
||||
Hoa_Acceptance_States, /// state-based acceptance if
|
||||
/// (globally) possible
|
||||
/// transition-based acceptance
|
||||
/// otherwise.
|
||||
Hoa_Acceptance_Transitions, /// transition-based acceptance globally
|
||||
Hoa_Acceptance_Mixed /// mix state-based and transition-based
|
||||
};
|
||||
|
||||
static std::ostream&
|
||||
print_hoa(std::ostream& os,
|
||||
const const_twa_graph_ptr& aut,
|
||||
const char* opt)
|
||||
{
|
||||
bool newline = true;
|
||||
hoa_acceptance acceptance = Hoa_Acceptance_States;
|
||||
bool implicit_labels = false;
|
||||
bool verbose = false;
|
||||
bool state_labels = false;
|
||||
|
||||
if (opt)
|
||||
while (*opt)
|
||||
{
|
||||
switch (char c = *opt++)
|
||||
{
|
||||
case 'i':
|
||||
implicit_labels = true;
|
||||
break;
|
||||
case 'k':
|
||||
state_labels = true;
|
||||
break;
|
||||
case 'l':
|
||||
newline = false;
|
||||
break;
|
||||
case 'm':
|
||||
acceptance = Hoa_Acceptance_Mixed;
|
||||
break;
|
||||
case 's':
|
||||
acceptance = Hoa_Acceptance_States;
|
||||
break;
|
||||
case 't':
|
||||
acceptance = Hoa_Acceptance_Transitions;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error
|
||||
(std::string("unknown option for print_hoa(): ") + c);
|
||||
}
|
||||
}
|
||||
|
||||
// Calling get_init_state_number() may add a state to empty
|
||||
// automata, so it has to be done first.
|
||||
unsigned init = aut->get_init_state_number();
|
||||
|
||||
metadata md(aut, implicit_labels, state_labels);
|
||||
|
||||
if (acceptance == Hoa_Acceptance_States && !md.has_state_acc)
|
||||
acceptance = Hoa_Acceptance_Transitions;
|
||||
|
||||
unsigned num_states = aut->num_states();
|
||||
|
||||
const char nl = newline ? '\n' : ' ';
|
||||
os << "HOA: v1" << nl;
|
||||
auto n = aut->get_named_prop<std::string>("automaton-name");
|
||||
if (n)
|
||||
escape_str(os << "name: \"", *n) << '"' << nl;
|
||||
unsigned nap = md.vap.size();
|
||||
os << "States: " << num_states << nl
|
||||
<< "Start: " << init << nl
|
||||
<< "AP: " << nap;
|
||||
auto d = aut->get_dict();
|
||||
for (auto& i: md.vap)
|
||||
escape_str(os << " \"", d->bdd_map[i].f.ap_name()) << '"';
|
||||
os << nl;
|
||||
|
||||
unsigned num_acc = aut->num_sets();
|
||||
acc_cond::acc_code acc_c = aut->acc().get_acceptance();
|
||||
if (aut->acc().is_generalized_buchi())
|
||||
{
|
||||
if (aut->acc().is_tt())
|
||||
os << "acc-name: all";
|
||||
else if (aut->acc().is_buchi())
|
||||
os << "acc-name: Buchi";
|
||||
else
|
||||
os << "acc-name: generalized-Buchi " << num_acc;
|
||||
os << nl;
|
||||
}
|
||||
else if (aut->acc().is_generalized_co_buchi())
|
||||
{
|
||||
if (aut->acc().is_ff())
|
||||
os << "acc-name: none";
|
||||
else if (aut->acc().is_co_buchi())
|
||||
os << "acc-name: co-Buchi";
|
||||
else
|
||||
os << "acc-name: generalized-co-Buchi " << num_acc;
|
||||
os << nl;
|
||||
}
|
||||
else
|
||||
{
|
||||
int r = aut->acc().is_rabin();
|
||||
assert(r != 0);
|
||||
if (r > 0)
|
||||
{
|
||||
os << "acc-name: Rabin " << r << nl;
|
||||
// Force the acceptance to remove any duplicate sets, and
|
||||
// make sure it is correctly ordered.
|
||||
acc_c = acc_cond::acc_code::rabin(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
r = aut->acc().is_streett();
|
||||
assert(r != 0);
|
||||
if (r > 0)
|
||||
{
|
||||
os << "acc-name: Streett " << r << nl;
|
||||
// Force the acceptance to remove any duplicate sets, and
|
||||
// make sure it is correctly ordered.
|
||||
acc_c = acc_cond::acc_code::streett(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<unsigned> pairs;
|
||||
if (aut->acc().is_generalized_rabin(pairs))
|
||||
{
|
||||
os << "acc-name: generalized-Rabin " << pairs.size();
|
||||
for (auto p: pairs)
|
||||
os << ' ' << p;
|
||||
os << nl;
|
||||
// Force the acceptance to remove any duplicate
|
||||
// sets, and make sure it is correctly ordered.
|
||||
acc_c = acc_cond::acc_code::generalized_rabin(pairs.begin(),
|
||||
pairs.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
bool max = false;
|
||||
bool odd = false;
|
||||
if (aut->acc().is_parity(max, odd))
|
||||
os << "acc-name: parity "
|
||||
<< (max ? "max " : "min ")
|
||||
<< (odd ? "odd " : "even ")
|
||||
<< num_acc << nl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
os << "Acceptance: " << num_acc << ' ';
|
||||
os << acc_c;
|
||||
os << nl;
|
||||
os << "properties:";
|
||||
// Make sure the property line is not too large,
|
||||
// otherwise our test cases do not fit in 80 columns...
|
||||
unsigned prop_len = 60;
|
||||
auto prop = [&](const char* str)
|
||||
{
|
||||
if (newline)
|
||||
{
|
||||
auto l = strlen(str);
|
||||
if (prop_len < l)
|
||||
{
|
||||
prop_len = 60;
|
||||
os << "\nproperties:";
|
||||
}
|
||||
prop_len -= l;
|
||||
}
|
||||
os << str;
|
||||
};
|
||||
// We do not support alternating automata so far, and it's
|
||||
// probable that nobody cares about the "no-univ-branch"
|
||||
// properties. The "univ-branch" properties seems more important
|
||||
// to announce that the automaton might not be parsable by tools
|
||||
// that do not support alternating automata.
|
||||
if (verbose)
|
||||
prop(" no-univ-branch");
|
||||
implicit_labels = md.use_implicit_labels;
|
||||
state_labels = md.use_state_labels;
|
||||
if (implicit_labels)
|
||||
prop(" implicit-labels");
|
||||
else if (state_labels)
|
||||
prop(" state-labels explicit-labels");
|
||||
else
|
||||
prop(" trans-labels explicit-labels");
|
||||
if (acceptance == Hoa_Acceptance_States)
|
||||
prop(" state-acc");
|
||||
else if (acceptance == Hoa_Acceptance_Transitions)
|
||||
prop(" trans-acc");
|
||||
if (md.is_colored)
|
||||
prop(" colored");
|
||||
if (md.is_complete)
|
||||
prop(" complete");
|
||||
if (md.is_deterministic)
|
||||
prop(" deterministic");
|
||||
// Deterministic automata are also unambiguous, so writing both
|
||||
// properties seems redundant. People working on unambiguous
|
||||
// automata are usually concerned about non-deterministic
|
||||
// unambiguous automata. So do not mention "unambiguous"
|
||||
// in the case of deterministic automata.
|
||||
if (aut->prop_unambiguous() && (verbose || !md.is_deterministic))
|
||||
prop(" unambiguous");
|
||||
assert(!(aut->prop_stutter_invariant() && aut->prop_stutter_sensitive()));
|
||||
if (aut->prop_stutter_invariant())
|
||||
prop(" stutter-invariant");
|
||||
if (aut->prop_stutter_sensitive())
|
||||
prop(" stutter-sensitive");
|
||||
if (aut->prop_terminal())
|
||||
prop(" terminal");
|
||||
if (aut->prop_weak() && (verbose || !aut->prop_terminal()))
|
||||
prop(" weak");
|
||||
if (aut->prop_inherently_weak() && (verbose || !aut->prop_weak()))
|
||||
prop(" inherently-weak");
|
||||
os << nl;
|
||||
|
||||
// If we want to output implicit labels, we have to
|
||||
// fill a vector with all destinations in order.
|
||||
std::vector<unsigned> out;
|
||||
std::vector<acc_cond::mark_t> outm;
|
||||
if (implicit_labels)
|
||||
{
|
||||
out.resize(1UL << nap);
|
||||
if (acceptance != Hoa_Acceptance_States)
|
||||
outm.resize(1UL << nap);
|
||||
}
|
||||
|
||||
os << "--BODY--" << nl;
|
||||
auto sn = aut->get_named_prop<std::vector<std::string>>("state-names");
|
||||
for (unsigned i = 0; i < num_states; ++i)
|
||||
{
|
||||
hoa_acceptance this_acc = acceptance;
|
||||
if (this_acc == Hoa_Acceptance_Mixed)
|
||||
this_acc = (md.common_acc[i] ?
|
||||
Hoa_Acceptance_States : Hoa_Acceptance_Transitions);
|
||||
|
||||
os << "State: ";
|
||||
if (state_labels)
|
||||
{
|
||||
bool output = false;
|
||||
for (auto& t: aut->out(i))
|
||||
{
|
||||
os << '[' << md.sup[t.cond] << "] ";
|
||||
output = true;
|
||||
break;
|
||||
}
|
||||
if (!output)
|
||||
os << "[f] ";
|
||||
}
|
||||
os << i;
|
||||
if (sn && i < sn->size() && !(*sn)[i].empty())
|
||||
os << " \"" << (*sn)[i] << '"';
|
||||
if (this_acc == Hoa_Acceptance_States)
|
||||
{
|
||||
acc_cond::mark_t acc = 0U;
|
||||
for (auto& t: aut->out(i))
|
||||
{
|
||||
acc = t.acc;
|
||||
break;
|
||||
}
|
||||
md.emit_acc(os, aut, acc);
|
||||
}
|
||||
os << nl;
|
||||
|
||||
if (!implicit_labels && !state_labels)
|
||||
{
|
||||
|
||||
for (auto& t: aut->out(i))
|
||||
{
|
||||
os << '[' << md.sup[t.cond] << "] " << t.dst;
|
||||
if (this_acc == Hoa_Acceptance_Transitions)
|
||||
md.emit_acc(os, aut, t.acc);
|
||||
os << nl;
|
||||
}
|
||||
}
|
||||
else if (state_labels)
|
||||
{
|
||||
unsigned n = 0;
|
||||
for (auto& t: aut->out(i))
|
||||
{
|
||||
os << t.dst;
|
||||
if (this_acc == Hoa_Acceptance_Transitions)
|
||||
{
|
||||
md.emit_acc(os, aut, t.acc);
|
||||
os << nl;
|
||||
}
|
||||
else
|
||||
{
|
||||
++n;
|
||||
os << (((n & 15) && t.next_succ) ? ' ' : nl);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& t: aut->out(i))
|
||||
{
|
||||
bdd cond = t.cond;
|
||||
while (cond != bddfalse)
|
||||
{
|
||||
bdd one = bdd_satoneset(cond, md.all_ap, bddfalse);
|
||||
cond -= one;
|
||||
unsigned level = 1;
|
||||
unsigned pos = 0U;
|
||||
while (one != bddtrue)
|
||||
{
|
||||
bdd h = bdd_high(one);
|
||||
if (h == bddfalse)
|
||||
{
|
||||
one = bdd_low(one);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos |= level;
|
||||
one = h;
|
||||
}
|
||||
level <<= 1;
|
||||
}
|
||||
out[pos] = t.dst;
|
||||
if (this_acc != Hoa_Acceptance_States)
|
||||
outm[pos] = t.acc;
|
||||
}
|
||||
}
|
||||
unsigned n = out.size();
|
||||
for (unsigned i = 0; i < n;)
|
||||
{
|
||||
os << out[i];
|
||||
if (this_acc != Hoa_Acceptance_States)
|
||||
{
|
||||
md.emit_acc(os, aut, outm[i]) << nl;
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
os << (((i & 15) && i < n) ? ' ' : nl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
os << "--END--"; // No newline. Let the caller decide.
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
print_hoa(std::ostream& os,
|
||||
const const_twa_ptr& aut,
|
||||
const char* opt)
|
||||
{
|
||||
|
||||
auto a = std::dynamic_pointer_cast<const twa_graph>(aut);
|
||||
if (!a)
|
||||
a = make_twa_graph(aut, twa::prop_set::all());
|
||||
|
||||
return print_hoa(os, a, opt);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 <iosfwd>
|
||||
#include "misc/common.hh"
|
||||
#include "twa/fwd.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_io
|
||||
/// \brief Print reachable states in Hanoi Omega Automata format.
|
||||
///
|
||||
/// \param os The output stream to print on.
|
||||
/// \param g The automaton to output.
|
||||
/// \param opt a set of characters each corresponding to a possible
|
||||
/// option: (i) implicit labels for complete and
|
||||
/// deterministic automata, (k) state labels when possible,
|
||||
/// (s) state-based acceptance when possible, (t)
|
||||
/// transition-based acceptance, (m) mixed acceptance, (l)
|
||||
/// single-line output, (v) verbose properties.
|
||||
SPOT_API std::ostream&
|
||||
print_hoa(std::ostream& os,
|
||||
const const_twa_ptr& g,
|
||||
const char* opt = nullptr);
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
#include "twaalgos/isdet.hh"
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template<bool count>
|
||||
static
|
||||
unsigned
|
||||
count_nondet_states_aux(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
unsigned nondet_states = 0;
|
||||
unsigned ns = aut->num_states();
|
||||
for (unsigned src = 0; src < ns; ++src)
|
||||
{
|
||||
bdd available = bddtrue;
|
||||
for (auto& t: aut->out(src))
|
||||
if (!bdd_implies(t.cond, available))
|
||||
{
|
||||
++nondet_states;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
available -= t.cond;
|
||||
}
|
||||
// If we are not counting non-deterministic states, abort as
|
||||
// soon as possible.
|
||||
if (!count && nondet_states)
|
||||
break;
|
||||
}
|
||||
return nondet_states;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned
|
||||
count_nondet_states(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
return count_nondet_states_aux<true>(aut);
|
||||
}
|
||||
|
||||
bool
|
||||
is_deterministic(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
if (aut->prop_deterministic())
|
||||
return true;
|
||||
return !count_nondet_states_aux<false>(aut);
|
||||
}
|
||||
|
||||
bool
|
||||
is_complete(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
unsigned ns = aut->num_states();
|
||||
for (unsigned src = 0; src < ns; ++src)
|
||||
{
|
||||
bdd available = bddtrue;
|
||||
for (auto& t: aut->out(src))
|
||||
available -= t.cond;
|
||||
if (available != bddfalse)
|
||||
return false;
|
||||
}
|
||||
// The empty automaton is not complete since it does not have an
|
||||
// initial state.
|
||||
return ns > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup twa_misc
|
||||
/// @{
|
||||
|
||||
/// \brief Count the number of non-deterministic states in \a aut.
|
||||
///
|
||||
/// The automaton is deterministic if it has 0 nondeterministic states,
|
||||
/// but it is more efficient to call is_deterministic() if you do not
|
||||
/// care about the number of nondeterministic states.
|
||||
SPOT_API unsigned
|
||||
count_nondet_states(const const_twa_graph_ptr& aut);
|
||||
|
||||
/// \brief Return true iff \a aut is deterministic.
|
||||
///
|
||||
/// This function is more efficient than count_nondet_states() when
|
||||
/// the automaton is nondeterministic, because it can return before
|
||||
/// the entire automaton has been explored.
|
||||
SPOT_API bool
|
||||
is_deterministic(const const_twa_graph_ptr& aut);
|
||||
|
||||
/// \brief Return true iff \a aut is complete.
|
||||
///
|
||||
/// An automaton is complete if its translation relation is total,
|
||||
/// i.e., each state as a successor for any possible configuration.
|
||||
SPOT_API bool
|
||||
is_complete(const const_twa_graph_ptr& aut);
|
||||
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 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 "isunamb.hh"
|
||||
#include "twaalgos/product.hh"
|
||||
#include "sccfilter.hh"
|
||||
#include <set>
|
||||
#include <list>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
bool is_unambiguous(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
if (aut->prop_unambiguous())
|
||||
return true;
|
||||
auto clean_a = scc_filter_states(aut);
|
||||
if (clean_a->num_edges() == 0)
|
||||
return true;
|
||||
auto prod = product(clean_a, clean_a);
|
||||
auto clean_p = scc_filter_states(prod);
|
||||
return clean_a->num_states() == clean_p->num_states()
|
||||
&& clean_a->num_edges() == clean_p->num_edges();
|
||||
}
|
||||
|
||||
bool check_unambiguous(const twa_graph_ptr& aut)
|
||||
{
|
||||
aut->prop_unambiguous(is_unambiguous(aut));
|
||||
return aut->prop_unambiguous();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
class tgba;
|
||||
|
||||
/// \addtogroup twa_misc
|
||||
/// @{
|
||||
|
||||
/// \brief Whether the automaton \a aut is unambiguous.
|
||||
///
|
||||
/// An automaton is unambiguous if each accepted word is
|
||||
/// recognized by only one path.
|
||||
///
|
||||
/// We check unambiguousity by synchronizing the automaton with
|
||||
/// itself, and then making sure that the co-reachable part of the
|
||||
/// squared automaton has the same size as the co-reachable part of
|
||||
/// the original automaton.
|
||||
SPOT_API bool
|
||||
is_unambiguous(const const_twa_graph_ptr& aut);
|
||||
|
||||
/// Like is_unambiguous(), but also sets the property in the twa.
|
||||
SPOT_API bool
|
||||
check_unambiguous(const twa_graph_ptr& aut);
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2012, 2013, 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 "cycles.hh"
|
||||
#include "tl/formula.hh"
|
||||
#include "isweakscc.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Look for a non-accepting cycle.
|
||||
class weak_checker final : public enumerate_cycles
|
||||
{
|
||||
public:
|
||||
bool result;
|
||||
|
||||
weak_checker(const scc_info& map)
|
||||
: enumerate_cycles(map), result(true)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool
|
||||
cycle_found(unsigned start) override
|
||||
{
|
||||
dfs_stack::const_reverse_iterator i = dfs_.rbegin();
|
||||
acc_cond::mark_t acc = 0U;
|
||||
for (;;)
|
||||
{
|
||||
acc |= aut_->edge_storage(i->succ).acc;
|
||||
if (i->s == start)
|
||||
break;
|
||||
++i;
|
||||
// The const cast is here to please old g++ versions.
|
||||
// At least version 4.0 needs it.
|
||||
assert(i != const_cast<const dfs_stack&>(dfs_).rend());
|
||||
}
|
||||
if (!aut_->acc().accepting(acc))
|
||||
{
|
||||
// We have found an non-accepting cycle, so the SCC is not
|
||||
// weak.
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
bool
|
||||
is_inherently_weak_scc(scc_info& map, unsigned scc)
|
||||
{
|
||||
// Weak SCCs are inherently weak.
|
||||
if (is_weak_scc(map, scc))
|
||||
return true;
|
||||
// If the SCC is accepting, but one cycle is not, the SCC is not
|
||||
// weak.
|
||||
weak_checker w(map);
|
||||
w.run(scc);
|
||||
return w.result;
|
||||
}
|
||||
|
||||
bool
|
||||
is_weak_scc(scc_info& map, unsigned scc)
|
||||
{
|
||||
// Rejecting SCCs are weak.
|
||||
if (map.is_rejecting_scc(scc))
|
||||
return true;
|
||||
// If all transitions use the same acceptance set, the SCC is weak.
|
||||
return map.used_acc_of(scc).size() == 1;
|
||||
}
|
||||
|
||||
bool
|
||||
is_complete_scc(scc_info& map, unsigned scc)
|
||||
{
|
||||
auto a = map.get_aut();
|
||||
for (auto s: map.states_of(scc))
|
||||
{
|
||||
bool has_succ = false;
|
||||
bdd sumall = bddfalse;
|
||||
for (auto& t: a->out(s))
|
||||
{
|
||||
has_succ = true;
|
||||
if (map.scc_of(t.dst) == scc)
|
||||
sumall |= t.cond;
|
||||
if (sumall == bddtrue)
|
||||
break;
|
||||
}
|
||||
if (!has_succ || sumall != bddtrue)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
is_terminal_scc(scc_info& map, unsigned scc)
|
||||
{
|
||||
// If all transitions use all acceptance conditions, the SCC is weak.
|
||||
return (map.is_accepting_scc(scc)
|
||||
&& map.used_acc_of(scc).size() == 1
|
||||
&& is_complete_scc(map, scc));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "sccinfo.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup twa_misc
|
||||
/// @{
|
||||
|
||||
/// \brief Whether the SCC number \a scc in \a map is inherently
|
||||
/// weak.
|
||||
///
|
||||
/// An SCC is inherently weak if either its cycles are all
|
||||
/// accepting, or they are all non-accepting.
|
||||
///
|
||||
/// Note the terminal SCCs are also inherently weak with that
|
||||
/// definition.
|
||||
///
|
||||
/// The absence of accepting cycle is easy to check (the scc_info
|
||||
/// object can tell whether the SCC is non-accepting already).
|
||||
/// Similarly, an SCC in which all transitions belong to all
|
||||
/// acceptance sets is necessarily weak. For other accepting SCCs,
|
||||
/// this function enumerates all cycles in the given SCC (it stops
|
||||
/// if it find a non-accepting cycle).
|
||||
SPOT_API bool
|
||||
is_inherently_weak_scc(scc_info& map, unsigned scc);
|
||||
|
||||
/// \brief Whether the SCC number \a scc in \a map is weak.
|
||||
///
|
||||
/// An SCC is weak if its non-accepting, or if all its transition
|
||||
/// are fully accepting (i.e., the belong to all acceptance sets).
|
||||
///
|
||||
/// Note that terminal SCCs are also weak with that definition.
|
||||
SPOT_API bool
|
||||
is_weak_scc(scc_info& map, unsigned scc);
|
||||
|
||||
/// \brief Whether the SCC number \a scc in \a map is complete.
|
||||
///
|
||||
/// An SCC is complete iff for all states and all label there exists
|
||||
/// a transition that stays into this SCC.
|
||||
SPOT_API bool
|
||||
is_complete_scc(scc_info& map, unsigned scc);
|
||||
|
||||
/// \brief Whether the SCC number \a scc in \a map is terminal.
|
||||
///
|
||||
/// An SCC is terminal if it is weak, complete, and accepting.
|
||||
SPOT_API bool
|
||||
is_terminal_scc(scc_info& map, unsigned scc);
|
||||
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2012, 2013, 2014, 2015 Laboratoire de Recherche
|
||||
// et Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 2004, 2005 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 "lbtt.hh"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include "twa/formula2bdd.hh"
|
||||
#include "reachiter.hh"
|
||||
#include "misc/bddlt.hh"
|
||||
#include "priv/accmap.hh"
|
||||
#include "tl/print.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class lbtt_bfs : public tgba_reachable_iterator_breadth_first
|
||||
{
|
||||
public:
|
||||
lbtt_bfs(const const_twa_ptr& a, std::ostream& os, bool sba_format)
|
||||
: tgba_reachable_iterator_breadth_first(a),
|
||||
os_(os),
|
||||
sba_format_(sba_format),
|
||||
sba_(nullptr)
|
||||
{
|
||||
// Check if the automaton can be converted into a
|
||||
// twa_graph. This makes the state_is_accepting() function
|
||||
// more efficient.
|
||||
if (a->is_sba())
|
||||
sba_ = std::dynamic_pointer_cast<const twa_graph>(a);
|
||||
}
|
||||
|
||||
acc_cond::mark_t
|
||||
state_acc_sets(const state *s) const
|
||||
{
|
||||
// If the automaton has a SBA type, it's easier to just query the
|
||||
// state_is_accepting() method.
|
||||
if (sba_)
|
||||
return sba_->state_acc_sets(sba_->state_number(s));
|
||||
|
||||
// Otherwise, since we are dealing with a degeneralized
|
||||
// automaton nonetheless, the transitions leaving an accepting
|
||||
// state are either all accepting, or all non-accepting. So
|
||||
// we just check the acceptance of the first transition. This
|
||||
// is not terribly efficient since we have to create the
|
||||
// iterator.
|
||||
twa_succ_iterator* it = aut_->succ_iter(s);
|
||||
if (!it->first())
|
||||
return {};
|
||||
auto res = it->acc();
|
||||
aut_->release_iter(it);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
process_state(const state* s, int n, twa_succ_iterator*)
|
||||
{
|
||||
--n;
|
||||
if (n == 0)
|
||||
body_ << "0 1";
|
||||
else
|
||||
body_ << "-1\n" << n << " 0";
|
||||
// Do we have state-based acceptance?
|
||||
if (sba_format_)
|
||||
{
|
||||
for (auto i: state_acc_sets(s).sets())
|
||||
body_ << ' ' << i;
|
||||
body_ << " -1";
|
||||
}
|
||||
body_ << '\n';
|
||||
}
|
||||
|
||||
void
|
||||
process_link(const state*, int,
|
||||
const state*, int out, const twa_succ_iterator* si)
|
||||
{
|
||||
body_ << out - 1 << ' ';
|
||||
if (!sba_format_)
|
||||
{
|
||||
for (auto s: aut_->acc().sets(si->acc()))
|
||||
body_ << s << ' ';
|
||||
body_ << "-1 ";
|
||||
}
|
||||
print_lbt_ltl(body_, bdd_to_formula(si->cond(),
|
||||
aut_->get_dict())) << '\n';
|
||||
}
|
||||
|
||||
void
|
||||
end()
|
||||
{
|
||||
os_ << seen.size() << ' ';
|
||||
if (sba_format_)
|
||||
os_ << aut_->num_sets();
|
||||
else
|
||||
os_ << aut_->num_sets() << 't';
|
||||
os_ << '\n' << body_.str() << "-1" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream& os_;
|
||||
std::ostringstream body_;
|
||||
bdd all_acc_conds_;
|
||||
bool sba_format_;
|
||||
const_twa_graph_ptr sba_;
|
||||
};
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
print_lbtt(std::ostream& os, const const_twa_ptr& g, const char* opt)
|
||||
{
|
||||
if (!g->acc().is_generalized_buchi())
|
||||
throw std::runtime_error
|
||||
("LBTT only supports generalized Büchi acceptance");
|
||||
|
||||
bool sba = g->prop_state_acc();
|
||||
if (opt)
|
||||
switch (char c = *opt++)
|
||||
{
|
||||
case 't':
|
||||
sba = false;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error
|
||||
(std::string("unknown option for print_lbtt(): ") + c);
|
||||
}
|
||||
|
||||
lbtt_bfs b(g, os, sba);
|
||||
b.run();
|
||||
return os;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2012, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "twa/twa.hh"
|
||||
#include <iosfwd>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_io
|
||||
/// \brief Print reachable states in LBTT's format.
|
||||
///
|
||||
/// \param g The automata to print.
|
||||
/// \param os Where to print.
|
||||
/// \param opt if "t", force transition-based acceptance, otherwise,
|
||||
// default to state-based acceptance when the automaton is marked so.
|
||||
SPOT_API std::ostream&
|
||||
print_lbtt(std::ostream& os, const const_twa_ptr& g,
|
||||
const char* opt = nullptr);
|
||||
}
|
||||
|
|
@ -1,402 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 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/>.
|
||||
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include "tl/unabbrev.hh"
|
||||
#include "tl/nenoform.hh"
|
||||
#include "tl/contain.hh"
|
||||
#include "ltl2taa.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
/// \brief Recursively translate a formula into a TAA.
|
||||
class ltl2taa_visitor
|
||||
{
|
||||
public:
|
||||
ltl2taa_visitor(const taa_tgba_formula_ptr& res,
|
||||
language_containment_checker* lcc,
|
||||
bool refined = false, bool negated = false)
|
||||
: res_(res), refined_(refined), negated_(negated),
|
||||
lcc_(lcc), init_(), succ_()
|
||||
{
|
||||
}
|
||||
|
||||
virtual
|
||||
~ltl2taa_visitor()
|
||||
{
|
||||
}
|
||||
|
||||
taa_tgba_formula_ptr&
|
||||
result()
|
||||
{
|
||||
res_->set_init_state(init_);
|
||||
return res_;
|
||||
}
|
||||
|
||||
void
|
||||
visit(formula f)
|
||||
{
|
||||
init_ = f;
|
||||
switch (f.kind())
|
||||
{
|
||||
case op::ff:
|
||||
return;
|
||||
case op::tt:
|
||||
{
|
||||
std::vector<formula> empty;
|
||||
res_->create_transition(init_, empty);
|
||||
succ_state ss = { empty, f, empty };
|
||||
succ_.push_back(ss);
|
||||
return;
|
||||
}
|
||||
case op::eword:
|
||||
SPOT_UNIMPLEMENTED();
|
||||
case op::ap:
|
||||
{
|
||||
if (negated_)
|
||||
f = formula::Not(f);
|
||||
init_ = f;
|
||||
std::vector<formula> empty;
|
||||
taa_tgba::transition* t = res_->create_transition(init_, empty);
|
||||
res_->add_condition(t, f);
|
||||
succ_state ss = { empty, f, empty };
|
||||
succ_.push_back(ss);
|
||||
return;
|
||||
}
|
||||
case op::X:
|
||||
{
|
||||
ltl2taa_visitor v = recurse(f[0]);
|
||||
std::vector<formula> dst;
|
||||
std::vector<formula> a;
|
||||
if (v.succ_.empty()) // Handle X(0)
|
||||
return;
|
||||
dst.push_back(v.init_);
|
||||
res_->create_transition(init_, dst);
|
||||
succ_state ss = { dst, formula::tt(), a };
|
||||
succ_.push_back(ss);
|
||||
return;
|
||||
}
|
||||
case op::F:
|
||||
case op::G:
|
||||
SPOT_UNIMPLEMENTED(); // TBD
|
||||
return;
|
||||
case op::Not:
|
||||
{
|
||||
negated_ = true;
|
||||
ltl2taa_visitor v = recurse(f[0]);
|
||||
// Done in recurse
|
||||
succ_ = v.succ_;
|
||||
return;
|
||||
}
|
||||
case op::Closure:
|
||||
case op::NegClosure:
|
||||
case op::NegClosureMarked:
|
||||
case op::Star:
|
||||
case op::FStar:
|
||||
case op::Xor:
|
||||
case op::Implies:
|
||||
case op::Equiv:
|
||||
case op::UConcat:
|
||||
case op::EConcat:
|
||||
case op::EConcatMarked:
|
||||
case op::Concat:
|
||||
case op::Fusion:
|
||||
case op::AndNLM:
|
||||
case op::AndRat:
|
||||
case op::OrRat:
|
||||
SPOT_UNIMPLEMENTED();
|
||||
|
||||
case op::U:
|
||||
case op::W:
|
||||
case op::R:
|
||||
case op::M:
|
||||
visit_binop(f);
|
||||
return;
|
||||
case op::And:
|
||||
case op::Or:
|
||||
visit_multop(f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
visit_binop(formula f)
|
||||
{
|
||||
ltl2taa_visitor v1 = recurse(f[0]);
|
||||
ltl2taa_visitor v2 = recurse(f[1]);
|
||||
|
||||
std::vector<succ_state>::iterator i1;
|
||||
std::vector<succ_state>::iterator i2;
|
||||
taa_tgba::transition* t = nullptr;
|
||||
bool contained = false;
|
||||
bool strong = false;
|
||||
|
||||
switch (f.kind())
|
||||
{
|
||||
case op::U:
|
||||
strong = true;
|
||||
// fall thru
|
||||
case op::W:
|
||||
if (refined_)
|
||||
contained = lcc_->contained(f[0], f[1]);
|
||||
for (i1 = v1.succ_.begin(); i1 != v1.succ_.end(); ++i1)
|
||||
{
|
||||
// Refined rule
|
||||
if (refined_ && contained)
|
||||
i1->Q.erase
|
||||
(remove(i1->Q.begin(), i1->Q.end(), v1.init_), i1->Q.end());
|
||||
|
||||
i1->Q.push_back(init_); // Add the initial state
|
||||
if (strong)
|
||||
i1->acc.push_back(f[1]);
|
||||
t = res_->create_transition(init_, i1->Q);
|
||||
res_->add_condition(t, i1->condition);
|
||||
if (strong)
|
||||
res_->add_acceptance_condition(t, f[1]);
|
||||
else
|
||||
for (unsigned i = 0; i < i1->acc.size(); ++i)
|
||||
res_->add_acceptance_condition(t, i1->acc[i]);
|
||||
succ_.push_back(*i1);
|
||||
}
|
||||
for (i2 = v2.succ_.begin(); i2 != v2.succ_.end(); ++i2)
|
||||
{
|
||||
t = res_->create_transition(init_, i2->Q);
|
||||
res_->add_condition(t, i2->condition);
|
||||
succ_.push_back(*i2);
|
||||
}
|
||||
return;
|
||||
case op::M: // Strong Release
|
||||
strong = true;
|
||||
case op::R: // Weak Release
|
||||
if (refined_)
|
||||
contained = lcc_->contained(f[0], f[1]);
|
||||
|
||||
for (i2 = v2.succ_.begin(); i2 != v2.succ_.end(); ++i2)
|
||||
{
|
||||
for (i1 = v1.succ_.begin(); i1 != v1.succ_.end(); ++i1)
|
||||
{
|
||||
std::vector<formula> u; // Union
|
||||
std::vector<formula> a; // Acceptance conditions
|
||||
std::copy(i1->Q.begin(), i1->Q.end(), ii(u, u.end()));
|
||||
formula f = i1->condition; // Refined rule
|
||||
if (!refined_ || !contained)
|
||||
{
|
||||
std::copy(i2->Q.begin(), i2->Q.end(), ii(u, u.end()));
|
||||
f = formula::And({f, i2->condition});
|
||||
}
|
||||
t = res_->create_transition(init_, u);
|
||||
res_->add_condition(t, f);
|
||||
succ_state ss = { u, f, a };
|
||||
succ_.push_back(ss);
|
||||
}
|
||||
|
||||
if (refined_) // Refined rule
|
||||
i2->Q.erase
|
||||
(remove(i2->Q.begin(), i2->Q.end(), v2.init_), i2->Q.end());
|
||||
|
||||
|
||||
i2->Q.push_back(init_); // Add the initial state
|
||||
t = res_->create_transition(init_, i2->Q);
|
||||
res_->add_condition(t, i2->condition);
|
||||
|
||||
if (strong)
|
||||
{
|
||||
i2->acc.push_back(f[0]);
|
||||
res_->add_acceptance_condition(t, f[0]);
|
||||
}
|
||||
else if (refined_)
|
||||
for (unsigned i = 0; i < i2->acc.size(); ++i)
|
||||
res_->add_acceptance_condition(t, i2->acc[i]);
|
||||
succ_.push_back(*i2);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
SPOT_UNIMPLEMENTED();
|
||||
}
|
||||
SPOT_UNREACHABLE();
|
||||
}
|
||||
|
||||
void
|
||||
visit_multop(formula f)
|
||||
{
|
||||
bool ok = true;
|
||||
std::vector<ltl2taa_visitor> vs;
|
||||
for (unsigned n = 0, s = f.size(); n < s; ++n)
|
||||
{
|
||||
vs.push_back(recurse(f[n]));
|
||||
if (vs[n].succ_.empty()) // Handle 0
|
||||
ok = false;
|
||||
}
|
||||
|
||||
std::vector<succ_state>::iterator i;
|
||||
taa_tgba::transition* t = nullptr;
|
||||
switch (f.kind())
|
||||
{
|
||||
case op::And:
|
||||
{
|
||||
if (!ok)
|
||||
return;
|
||||
std::vector<succ_state> p = all_n_tuples(vs);
|
||||
for (unsigned n = 0; n < p.size(); ++n)
|
||||
{
|
||||
if (refined_)
|
||||
{
|
||||
std::vector<formula> v; // All sub initial states.
|
||||
sort(p[n].Q.begin(), p[n].Q.end());
|
||||
for (unsigned m = 0; m < f.size(); ++m)
|
||||
{
|
||||
if (!binary_search(p[n].Q.begin(), p[n].Q.end(),
|
||||
vs[m].init_))
|
||||
break;
|
||||
v.push_back(vs[m].init_);
|
||||
}
|
||||
|
||||
if (v.size() == f.size())
|
||||
{
|
||||
std::vector<formula> Q;
|
||||
sort(v.begin(), v.end());
|
||||
for (unsigned m = 0; m < p[n].Q.size(); ++m)
|
||||
if (!binary_search(v.begin(), v.end(), p[n].Q[m]))
|
||||
Q.push_back(p[n].Q[m]);
|
||||
Q.push_back(init_);
|
||||
t = res_->create_transition(init_, Q);
|
||||
res_->add_condition(t, p[n].condition);
|
||||
for (unsigned i = 0; i < p[n].acc.size(); ++i)
|
||||
res_->add_acceptance_condition(t, p[n].acc[i]);
|
||||
succ_.push_back(p[n]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
t = res_->create_transition(init_, p[n].Q);
|
||||
res_->add_condition(t, p[n].condition);
|
||||
succ_.push_back(p[n]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case op::Or:
|
||||
for (unsigned n = 0, s = f.size(); n < s; ++n)
|
||||
for (auto i: vs[n].succ_)
|
||||
{
|
||||
t = res_->create_transition(init_, i.Q);
|
||||
res_->add_condition(t, i.condition);
|
||||
succ_.push_back(i);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
SPOT_UNIMPLEMENTED();
|
||||
}
|
||||
SPOT_UNREACHABLE();
|
||||
}
|
||||
|
||||
ltl2taa_visitor
|
||||
recurse(formula f)
|
||||
{
|
||||
ltl2taa_visitor v(res_, lcc_, refined_, negated_);
|
||||
v.visit(f);
|
||||
return v;
|
||||
}
|
||||
|
||||
private:
|
||||
taa_tgba_formula_ptr res_;
|
||||
bool refined_;
|
||||
bool negated_;
|
||||
language_containment_checker* lcc_;
|
||||
|
||||
typedef std::insert_iterator<std::vector<formula>> ii;
|
||||
|
||||
struct succ_state
|
||||
{
|
||||
std::vector<formula> Q; // States
|
||||
formula condition;
|
||||
std::vector<formula> acc;
|
||||
};
|
||||
|
||||
formula init_;
|
||||
std::vector<succ_state> succ_;
|
||||
|
||||
public:
|
||||
std::vector<succ_state>
|
||||
all_n_tuples(const std::vector<ltl2taa_visitor>& vs)
|
||||
{
|
||||
std::vector<succ_state> product;
|
||||
|
||||
std::vector<int> pos(vs.size());
|
||||
for (unsigned i = 0; i < vs.size(); ++i)
|
||||
pos[i] = vs[i].succ_.size();
|
||||
|
||||
while (pos[0] != 0)
|
||||
{
|
||||
std::vector<formula> u; // Union
|
||||
std::vector<formula> a; // Acceptance conditions
|
||||
formula f = formula::tt();
|
||||
for (unsigned i = 0; i < vs.size(); ++i)
|
||||
{
|
||||
if (vs[i].succ_.empty())
|
||||
continue;
|
||||
const succ_state& ss(vs[i].succ_[pos[i] - 1]);
|
||||
std::copy(ss.Q.begin(), ss.Q.end(), ii(u, u.end()));
|
||||
f = formula::And({ss.condition, f});
|
||||
for (unsigned i = 0; i < ss.acc.size(); ++i)
|
||||
{
|
||||
formula g = ss.acc[i];
|
||||
a.push_back(g);
|
||||
}
|
||||
}
|
||||
succ_state ss = { u, f, a };
|
||||
product.push_back(ss);
|
||||
|
||||
for (int i = vs.size() - 1; i >= 0; --i)
|
||||
{
|
||||
if (vs[i].succ_.empty())
|
||||
continue;
|
||||
if (pos[i] > 1 || (i == 0 && pos[0] == 1))
|
||||
{
|
||||
--pos[i];
|
||||
break;
|
||||
}
|
||||
else
|
||||
pos[i] = vs[i].succ_.size();
|
||||
}
|
||||
}
|
||||
return product;
|
||||
}
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
taa_tgba_formula_ptr
|
||||
ltl_to_taa(formula f,
|
||||
const bdd_dict_ptr& dict, bool refined_rules)
|
||||
{
|
||||
// TODO: implement translation of F and G
|
||||
auto f2 = negative_normal_form(unabbreviate(f, "^ieFG"));
|
||||
auto res = make_taa_tgba_formula(dict);
|
||||
language_containment_checker* lcc =
|
||||
new language_containment_checker(make_bdd_dict(),
|
||||
false, false, false, false);
|
||||
ltl2taa_visitor v(res, lcc, refined_rules);
|
||||
v.visit(f2);
|
||||
auto taa = v.result();
|
||||
delete lcc;
|
||||
taa->acc().set_generalized_buchi();
|
||||
return taa;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 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 "tl/formula.hh"
|
||||
#include "twa/taatgba.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_ltl
|
||||
/// \brief Build a spot::taa* from an LTL formula.
|
||||
///
|
||||
/// This is based on the following.
|
||||
/** \verbatim
|
||||
@techreport{HUT-TCS-A104,
|
||||
address = {Espoo, Finland},
|
||||
author = {Heikki Tauriainen},
|
||||
month = {September},
|
||||
note = {Doctoral dissertation},
|
||||
number = {A104},
|
||||
pages = {xii+229},
|
||||
title = {Automata and Linear Temporal Logic: Translations
|
||||
with Transition-Based Acceptance},
|
||||
type = {Research Report},
|
||||
year = {2006}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \param f The formula to translate into an automaton.
|
||||
/// \param dict The spot::bdd_dict the constructed automata should use.
|
||||
/// \param refined_rules If this parameter is set, refined rules are used.
|
||||
/// \return A spot::taa that recognizes the language of \a f.
|
||||
SPOT_API taa_tgba_formula_ptr
|
||||
ltl_to_taa(formula f, const bdd_dict_ptr& dict,
|
||||
bool refined_rules = false);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,150 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 Laboratoire de
|
||||
// Recherche et Développement 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 "tl/formula.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "tl/apcollect.hh"
|
||||
#include "tl/simplify.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_ltl
|
||||
/// \brief Build a spot::twa_graph_ptr from an LTL formula.
|
||||
///
|
||||
/// This is based on the following paper.
|
||||
/** \verbatim
|
||||
@InProceedings{couvreur.99.fm,
|
||||
author = {Jean-Michel Couvreur},
|
||||
title = {On-the-fly Verification of Temporal Logic},
|
||||
pages = {253--271},
|
||||
editor = {Jeannette M. Wing and Jim Woodcock and Jim Davies},
|
||||
booktitle = {Proceedings of the World Congress on Formal Methods in the
|
||||
Development of Computing Systems (FM'99)},
|
||||
publisher = {Springer-Verlag},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
volume = {1708},
|
||||
year = {1999},
|
||||
address = {Toulouse, France},
|
||||
month = {September},
|
||||
isbn = {3-540-66587-0}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \param f The formula to translate into an automaton.
|
||||
///
|
||||
/// \param dict The spot::bdd_dict the constructed automata should use.
|
||||
///
|
||||
/// \param exprop When set, the algorithm will consider all properties
|
||||
/// combinations possible on each state, in an attempt to reduce
|
||||
/// the non-determinism. The automaton will have the same size as
|
||||
/// without this option, but because the transition will be more
|
||||
/// deterministic, the product automaton will be smaller (or, at worse,
|
||||
/// equal).
|
||||
///
|
||||
/// \param symb_merge When false, states with the same symbolic
|
||||
/// representation (these are equivalent formulae) will not be
|
||||
/// merged.
|
||||
///
|
||||
/// \param branching_postponement When set, several transitions leaving
|
||||
/// from the same state with the same label (i.e., condition + acceptance
|
||||
/// conditions) will be merged. This correspond to an optimization
|
||||
/// described in the following paper.
|
||||
/** \verbatim
|
||||
@InProceedings{ sebastiani.03.charme,
|
||||
author = {Roberto Sebastiani and Stefano Tonetta},
|
||||
title = {"More Deterministic" vs. "Smaller" B{\"u}chi Automata for
|
||||
Efficient LTL Model Checking},
|
||||
booktitle = {Proceedings for the 12th Advanced Research Working
|
||||
Conference on Correct Hardware Design and Verification
|
||||
Methods (CHARME'03)},
|
||||
pages = {126--140},
|
||||
year = {2003},
|
||||
editor = {G. Goos and J. Hartmanis and J. van Leeuwen},
|
||||
volume = {2860},
|
||||
series = {Lectures Notes in Computer Science},
|
||||
month = {October},
|
||||
publisher = {Springer-Verlag}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \param fair_loop_approx When set, a really simple characterization of
|
||||
/// unstable state is used to suppress all acceptance conditions from
|
||||
/// incoming transitions.
|
||||
///
|
||||
/// \param unobs When non-zero, the atomic propositions in the LTL formula
|
||||
/// are interpreted as events that exclude each other. The events in the
|
||||
/// formula are observable events, and \c unobs can be filled with
|
||||
/// additional unobservable events.
|
||||
///
|
||||
/// \param simplifier If this parameter is set, the LTL formulae
|
||||
/// representing each state of the automaton will be simplified
|
||||
/// before computing the successor. \a simpl should be configured
|
||||
/// for the type of reduction you want, see
|
||||
/// spot::tl_simplifier. This idea is taken from the
|
||||
/// following paper.
|
||||
/** \verbatim
|
||||
@InProceedings{ thirioux.02.fmics,
|
||||
author = {Xavier Thirioux},
|
||||
title = {Simple and Efficient Translation from {LTL} Formulas to
|
||||
{B\"u}chi Automata},
|
||||
booktitle = {Proceedings of the 7th International ERCIM Workshop in
|
||||
Formal Methods for Industrial Critical Systems (FMICS'02)},
|
||||
series = {Electronic Notes in Theoretical Computer Science},
|
||||
volume = {66(2)},
|
||||
publisher = {Elsevier},
|
||||
editor = {Rance Cleaveland and Hubert Garavel},
|
||||
year = {2002},
|
||||
month = jul,
|
||||
address = {M{\'a}laga, Spain}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \param unambiguous When true, unambigous TGBA will be produced using
|
||||
/// the trick described in the following paper.
|
||||
/** \verbatim
|
||||
@InProceedings{ benedikt.13.tacas,
|
||||
author = {Michael Benedikt and Rastislav Lenhardt and James
|
||||
Worrell},
|
||||
title = {{LTL} Model Checking of Interval Markov Chains},
|
||||
booktitle = {19th International Conference on Tools and Algorithms for
|
||||
the Construction and Analysis of Systems (TACAS'13)},
|
||||
year = {2013},
|
||||
pages = {32--46},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
volume = {7795},
|
||||
editor = {Nir Piterman and Scott A. Smolka},
|
||||
publisher = {Springer}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \return A spot::twa_graph that recognizes the language of \a f.
|
||||
SPOT_API twa_graph_ptr
|
||||
ltl_to_tgba_fm(formula f, const bdd_dict_ptr& dict,
|
||||
bool exprop = false, bool symb_merge = true,
|
||||
bool branching_postponement = false,
|
||||
bool fair_loop_approx = false,
|
||||
const atomic_prop_set* unobs = nullptr,
|
||||
tl_simplifier* simplifier = nullptr,
|
||||
bool unambiguous = false);
|
||||
}
|
||||
|
|
@ -1,608 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014, 2015 Laboratoire de recherche et
|
||||
// développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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/>.
|
||||
|
||||
//#define TRACE
|
||||
|
||||
#include <iostream>
|
||||
#ifdef TRACE
|
||||
#define trace std::cerr
|
||||
#else
|
||||
#define trace while (0) std::cerr
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
#include "misc/hash.hh"
|
||||
#include "twa/twa.hh"
|
||||
#include "emptiness.hh"
|
||||
#include "emptiness_stats.hh"
|
||||
#include "magic.hh"
|
||||
#include "ndfs_result.hxx"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
enum color {WHITE, BLUE, RED};
|
||||
|
||||
/// \brief Emptiness checker on spot::tgba automata having at most one
|
||||
/// acceptance condition (i.e. a TBA).
|
||||
template <typename heap>
|
||||
class magic_search_ : public emptiness_check, public ec_statistics
|
||||
{
|
||||
public:
|
||||
/// \brief Initialize the Magic Search algorithm on the automaton \a a
|
||||
///
|
||||
/// \pre The automaton \a a must have at most one acceptance
|
||||
/// condition (i.e. it is a TBA).
|
||||
magic_search_(const const_twa_ptr& a, size_t size,
|
||||
option_map o = option_map())
|
||||
: emptiness_check(a, o),
|
||||
h(size)
|
||||
{
|
||||
assert(a->num_sets() <= 1);
|
||||
}
|
||||
|
||||
virtual ~magic_search_()
|
||||
{
|
||||
// Release all iterators on the stacks.
|
||||
while (!st_blue.empty())
|
||||
{
|
||||
h.pop_notify(st_blue.front().s);
|
||||
a_->release_iter(st_blue.front().it);
|
||||
st_blue.pop_front();
|
||||
}
|
||||
while (!st_red.empty())
|
||||
{
|
||||
h.pop_notify(st_red.front().s);
|
||||
a_->release_iter(st_red.front().it);
|
||||
st_red.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Perform a Magic Search.
|
||||
///
|
||||
/// \return non null pointer iff the algorithm has found a
|
||||
/// new accepting path.
|
||||
///
|
||||
/// check() can be called several times (until it returns a null
|
||||
/// pointer) to enumerate all the visited accepting paths. The method
|
||||
/// visits only a finite set of accepting paths.
|
||||
virtual emptiness_check_result_ptr check()
|
||||
{
|
||||
auto t = std::static_pointer_cast<magic_search_>
|
||||
(this->emptiness_check::shared_from_this());
|
||||
if (st_red.empty())
|
||||
{
|
||||
assert(st_blue.empty());
|
||||
const state* s0 = a_->get_init_state();
|
||||
inc_states();
|
||||
h.add_new_state(s0, BLUE);
|
||||
push(st_blue, s0, bddfalse, 0U);
|
||||
if (dfs_blue())
|
||||
return std::make_shared<magic_search_result>(t, options());
|
||||
}
|
||||
else
|
||||
{
|
||||
h.pop_notify(st_red.front().s);
|
||||
pop(st_red);
|
||||
if (!st_red.empty() && dfs_red())
|
||||
return std::make_shared<magic_search_result>(t, options());
|
||||
else
|
||||
if (dfs_blue())
|
||||
return std::make_shared<magic_search_result>(t, options());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual std::ostream& print_stats(std::ostream &os) const
|
||||
{
|
||||
os << states() << " distinct nodes visited" << std::endl;
|
||||
os << transitions() << " transitions explored" << std::endl;
|
||||
os << max_depth() << " nodes for the maximal stack depth" << std::endl;
|
||||
if (!st_red.empty())
|
||||
{
|
||||
assert(!st_blue.empty());
|
||||
os << st_blue.size() + st_red.size() - 1
|
||||
<< " nodes for the counter example" << std::endl;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
virtual bool safe() const
|
||||
{
|
||||
return heap::Safe;
|
||||
}
|
||||
|
||||
const heap& get_heap() const
|
||||
{
|
||||
return h;
|
||||
}
|
||||
|
||||
const stack_type& get_st_blue() const
|
||||
{
|
||||
return st_blue;
|
||||
}
|
||||
|
||||
const stack_type& get_st_red() const
|
||||
{
|
||||
return st_red;
|
||||
}
|
||||
private:
|
||||
|
||||
void push(stack_type& st, const state* s,
|
||||
const bdd& label, acc_cond::mark_t acc)
|
||||
{
|
||||
inc_depth();
|
||||
twa_succ_iterator* i = a_->succ_iter(s);
|
||||
i->first();
|
||||
st.emplace_front(s, i, label, acc);
|
||||
}
|
||||
|
||||
void pop(stack_type& st)
|
||||
{
|
||||
dec_depth();
|
||||
a_->release_iter(st.front().it);
|
||||
st.pop_front();
|
||||
}
|
||||
|
||||
/// \brief Stack of the blue dfs.
|
||||
stack_type st_blue;
|
||||
|
||||
/// \brief Stack of the red dfs.
|
||||
stack_type st_red;
|
||||
|
||||
/// \brief Map where each visited state is colored
|
||||
/// by the last dfs visiting it.
|
||||
heap h;
|
||||
|
||||
/// State targeted by the red dfs.
|
||||
const state* target;
|
||||
|
||||
bool dfs_blue()
|
||||
{
|
||||
while (!st_blue.empty())
|
||||
{
|
||||
stack_item& f = st_blue.front();
|
||||
trace << "DFS_BLUE treats: " << a_->format_state(f.s) << std::endl;
|
||||
if (!f.it->done())
|
||||
{
|
||||
const state *s_prime = f.it->dst();
|
||||
trace << " Visit the successor: "
|
||||
<< a_->format_state(s_prime) << std::endl;
|
||||
bdd label = f.it->cond();
|
||||
auto acc = f.it->acc();
|
||||
// Go down the edge (f.s, <label, acc>, s_prime)
|
||||
f.it->next();
|
||||
inc_transitions();
|
||||
typename heap::color_ref c = h.get_color_ref(s_prime);
|
||||
if (c.is_white())
|
||||
{
|
||||
trace << " It is white, go down" << std::endl;
|
||||
inc_states();
|
||||
h.add_new_state(s_prime, BLUE);
|
||||
push(st_blue, s_prime, label, acc);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a_->acc().accepting(acc) && c.get_color() != RED)
|
||||
{
|
||||
// the test 'c.get_color() != RED' is added to limit
|
||||
// the number of runs reported by successive
|
||||
// calls to the check method. Without this
|
||||
// functionnality, the test can be ommited.
|
||||
trace << " It is blue and the arc is "
|
||||
<< "accepting, start a red dfs" << std::endl;
|
||||
target = f.s;
|
||||
c.set_color(RED);
|
||||
push(st_red, s_prime, label, acc);
|
||||
if (dfs_red())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << " It is blue or red, pop it" << std::endl;
|
||||
h.pop_notify(s_prime);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
// Backtrack the edge
|
||||
// (predecessor of f.s in st_blue, <f.label, f.acc>, f.s)
|
||||
{
|
||||
trace << " All the successors have been visited" << std::endl;
|
||||
stack_item f_dest(f);
|
||||
pop(st_blue);
|
||||
typename heap::color_ref c = h.get_color_ref(f_dest.s);
|
||||
assert(!c.is_white());
|
||||
if (!st_blue.empty() &&
|
||||
a_->acc().accepting(f_dest.acc) && c.get_color() != RED)
|
||||
{
|
||||
// the test 'c.get_color() != RED' is added to limit
|
||||
// the number of runs reported by successive
|
||||
// calls to the check method. Without this
|
||||
// functionnality, the test can be ommited.
|
||||
trace << " It is blue and the arc from "
|
||||
<< a_->format_state(st_blue.front().s)
|
||||
<< " to it is accepting, start a red dfs"
|
||||
<< std::endl;
|
||||
target = st_blue.front().s;
|
||||
c.set_color(RED);
|
||||
push(st_red, f_dest.s, f_dest.label, f_dest.acc);
|
||||
if (dfs_red())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << " Pop it" << std::endl;
|
||||
h.pop_notify(f_dest.s);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dfs_red()
|
||||
{
|
||||
assert(!st_red.empty());
|
||||
if (target->compare(st_red.front().s) == 0)
|
||||
return true;
|
||||
|
||||
while (!st_red.empty())
|
||||
{
|
||||
stack_item& f = st_red.front();
|
||||
trace << "DFS_RED treats: " << a_->format_state(f.s) << std::endl;
|
||||
if (!f.it->done())
|
||||
{
|
||||
const state *s_prime = f.it->dst();
|
||||
trace << " Visit the successor: "
|
||||
<< a_->format_state(s_prime) << std::endl;
|
||||
bdd label = f.it->cond();
|
||||
auto acc = f.it->acc();
|
||||
// Go down the edge (f.s, <label, acc>, s_prime)
|
||||
f.it->next();
|
||||
inc_transitions();
|
||||
typename heap::color_ref c = h.get_color_ref(s_prime);
|
||||
if (c.is_white())
|
||||
{
|
||||
// If the red dfs find a white here, it must have crossed
|
||||
// the blue stack and the target must be reached soon.
|
||||
// Notice that this property holds only for explicit search.
|
||||
// Collisions in bit-state hashing search can also lead
|
||||
// to the visit of white state. Anyway, it is not necessary
|
||||
// to visit white states either if a cycle can be missed
|
||||
// with bit-state hashing search.
|
||||
trace << " It is white, pop it" << std::endl;
|
||||
s_prime->destroy();
|
||||
}
|
||||
else if (c.get_color() == BLUE)
|
||||
{
|
||||
trace << " It is blue, go down" << std::endl;
|
||||
c.set_color(RED);
|
||||
push(st_red, s_prime, label, acc);
|
||||
if (target->compare(s_prime) == 0)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << " It is red, pop it" << std::endl;
|
||||
h.pop_notify(s_prime);
|
||||
}
|
||||
}
|
||||
else // Backtrack
|
||||
{
|
||||
trace << " All the successors have been visited, pop it"
|
||||
<< std::endl;
|
||||
h.pop_notify(f.s);
|
||||
pop(st_red);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class result_from_stack: public emptiness_check_result,
|
||||
public acss_statistics
|
||||
{
|
||||
public:
|
||||
result_from_stack(std::shared_ptr<magic_search_> ms)
|
||||
: emptiness_check_result(ms->automaton()), ms_(ms)
|
||||
{
|
||||
}
|
||||
|
||||
virtual twa_run_ptr accepting_run()
|
||||
{
|
||||
assert(!ms_->st_blue.empty());
|
||||
assert(!ms_->st_red.empty());
|
||||
|
||||
auto run = std::make_shared<twa_run>(automaton());
|
||||
|
||||
typename stack_type::const_reverse_iterator i, j, end;
|
||||
twa_run::steps* l;
|
||||
|
||||
l = &run->prefix;
|
||||
|
||||
i = ms_->st_blue.rbegin();
|
||||
end = ms_->st_blue.rend(); --end;
|
||||
j = i; ++j;
|
||||
for (; i != end; ++i, ++j)
|
||||
{
|
||||
twa_run::step s = { i->s->clone(), j->label, j->acc };
|
||||
l->push_back(s);
|
||||
}
|
||||
|
||||
l = &run->cycle;
|
||||
|
||||
j = ms_->st_red.rbegin();
|
||||
twa_run::step s = { i->s->clone(), j->label, j->acc };
|
||||
l->push_back(s);
|
||||
|
||||
i = j; ++j;
|
||||
end = ms_->st_red.rend(); --end;
|
||||
for (; i != end; ++i, ++j)
|
||||
{
|
||||
twa_run::step s = { i->s->clone(), j->label, j->acc };
|
||||
l->push_back(s);
|
||||
}
|
||||
|
||||
return run;
|
||||
}
|
||||
|
||||
unsigned acss_states() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<magic_search_> ms_;
|
||||
};
|
||||
|
||||
# define FROM_STACK "ar:from_stack"
|
||||
|
||||
class magic_search_result: public emptiness_check_result
|
||||
{
|
||||
public:
|
||||
magic_search_result(const std::shared_ptr<magic_search_>& m,
|
||||
option_map o = option_map())
|
||||
: emptiness_check_result(m->automaton(), o), ms(m)
|
||||
{
|
||||
if (options()[FROM_STACK])
|
||||
computer = new result_from_stack(ms);
|
||||
else
|
||||
computer = new ndfs_result<magic_search_<heap>, heap>(ms);
|
||||
}
|
||||
|
||||
virtual void options_updated(const option_map& old)
|
||||
{
|
||||
if (old[FROM_STACK] && !options()[FROM_STACK])
|
||||
{
|
||||
delete computer;
|
||||
computer = new ndfs_result<magic_search_<heap>, heap>(ms);
|
||||
}
|
||||
else if (!old[FROM_STACK] && options()[FROM_STACK])
|
||||
{
|
||||
delete computer;
|
||||
computer = new result_from_stack(ms);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~magic_search_result()
|
||||
{
|
||||
delete computer;
|
||||
}
|
||||
|
||||
virtual twa_run_ptr accepting_run()
|
||||
{
|
||||
return computer->accepting_run();
|
||||
}
|
||||
|
||||
virtual const unsigned_statistics* statistics() const
|
||||
{
|
||||
return computer->statistics();
|
||||
}
|
||||
|
||||
private:
|
||||
emptiness_check_result* computer;
|
||||
std::shared_ptr<magic_search_> ms;
|
||||
};
|
||||
};
|
||||
|
||||
class explicit_magic_search_heap
|
||||
{
|
||||
public:
|
||||
enum { Safe = 1 };
|
||||
|
||||
class color_ref
|
||||
{
|
||||
public:
|
||||
color_ref(color* c) :p(c)
|
||||
{
|
||||
}
|
||||
color get_color() const
|
||||
{
|
||||
return *p;
|
||||
}
|
||||
void set_color(color c)
|
||||
{
|
||||
assert(!is_white());
|
||||
*p=c;
|
||||
}
|
||||
bool is_white() const
|
||||
{
|
||||
return !p;
|
||||
}
|
||||
private:
|
||||
color *p;
|
||||
};
|
||||
|
||||
explicit_magic_search_heap(size_t)
|
||||
{
|
||||
}
|
||||
|
||||
~explicit_magic_search_heap()
|
||||
{
|
||||
hash_type::const_iterator s = h.begin();
|
||||
while (s != h.end())
|
||||
{
|
||||
// Advance the iterator before deleting the "key" pointer.
|
||||
const state* ptr = s->first;
|
||||
++s;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
color_ref get_color_ref(const state*& s)
|
||||
{
|
||||
hash_type::iterator it = h.find(s);
|
||||
if (it == h.end())
|
||||
return color_ref(nullptr);
|
||||
if (s != it->first)
|
||||
{
|
||||
s->destroy();
|
||||
s = it->first;
|
||||
}
|
||||
return color_ref(&it->second);
|
||||
}
|
||||
|
||||
void add_new_state(const state* s, color c)
|
||||
{
|
||||
assert(h.find(s) == h.end());
|
||||
h.emplace(s, c);
|
||||
}
|
||||
|
||||
void pop_notify(const state*) const
|
||||
{
|
||||
}
|
||||
|
||||
bool has_been_visited(const state* s) const
|
||||
{
|
||||
hash_type::const_iterator it = h.find(s);
|
||||
return (it != h.end());
|
||||
}
|
||||
|
||||
enum { Has_Size = 1 };
|
||||
int size() const
|
||||
{
|
||||
return h.size();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
typedef std::unordered_map<const state*, color,
|
||||
state_ptr_hash, state_ptr_equal> hash_type;
|
||||
hash_type h;
|
||||
};
|
||||
|
||||
class bsh_magic_search_heap
|
||||
{
|
||||
public:
|
||||
enum { Safe = 0 };
|
||||
|
||||
class color_ref
|
||||
{
|
||||
public:
|
||||
color_ref(unsigned char *b, unsigned char o): base(b), offset(o*2)
|
||||
{
|
||||
}
|
||||
color get_color() const
|
||||
{
|
||||
return color(((*base) >> offset) & 3U);
|
||||
}
|
||||
void set_color(color c)
|
||||
{
|
||||
*base = (*base & ~(3U << offset)) | (c << offset);
|
||||
}
|
||||
bool is_white() const
|
||||
{
|
||||
return get_color() == WHITE;
|
||||
}
|
||||
private:
|
||||
unsigned char *base;
|
||||
unsigned char offset;
|
||||
};
|
||||
|
||||
bsh_magic_search_heap(size_t s)
|
||||
{
|
||||
size_ = s;
|
||||
h = new unsigned char[size_];
|
||||
memset(h, WHITE, size_);
|
||||
}
|
||||
|
||||
~bsh_magic_search_heap()
|
||||
{
|
||||
delete[] h;
|
||||
}
|
||||
|
||||
color_ref get_color_ref(const state*& s)
|
||||
{
|
||||
size_t ha = s->hash();
|
||||
return color_ref(&(h[ha%size_]), ha%4);
|
||||
}
|
||||
|
||||
void add_new_state(const state* s, color c)
|
||||
{
|
||||
color_ref cr(get_color_ref(s));
|
||||
assert(cr.is_white());
|
||||
cr.set_color(c);
|
||||
}
|
||||
|
||||
void pop_notify(const state* s) const
|
||||
{
|
||||
s->destroy();
|
||||
}
|
||||
|
||||
bool has_been_visited(const state* s) const
|
||||
{
|
||||
size_t ha = s->hash();
|
||||
return color((h[ha%size_] >> ((ha%4)*2)) & 3U) != WHITE;
|
||||
}
|
||||
|
||||
enum { Has_Size = 0 };
|
||||
|
||||
private:
|
||||
size_t size_;
|
||||
unsigned char* h;
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
emptiness_check_ptr
|
||||
explicit_magic_search(const const_twa_ptr& a, option_map o)
|
||||
{
|
||||
return std::make_shared<magic_search_<explicit_magic_search_heap>>(a, 0, o);
|
||||
}
|
||||
|
||||
emptiness_check_ptr
|
||||
bit_state_hashing_magic_search(const const_twa_ptr& a,
|
||||
size_t size, option_map o)
|
||||
{
|
||||
return std::make_shared<magic_search_<bsh_magic_search_heap>>(a, size, o);
|
||||
}
|
||||
|
||||
emptiness_check_ptr
|
||||
magic_search(const const_twa_ptr& a, option_map o)
|
||||
{
|
||||
size_t size = o.get("bsh");
|
||||
if (size)
|
||||
return bit_state_hashing_magic_search(a, size, o);
|
||||
return explicit_magic_search(a, o);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014 Laboratoire de Recherche et Developpement
|
||||
// de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 <cstddef>
|
||||
#include "twa/fwd.hh"
|
||||
#include "misc/optionmap.hh"
|
||||
#include "emptiness.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup emptiness_check_algorithms
|
||||
/// @{
|
||||
|
||||
/// \brief Returns an emptiness checker on the spot::tgba automaton \a a.
|
||||
///
|
||||
/// \pre The automaton \a a must have at most one acceptance condition (i.e.
|
||||
/// it is a TBA).
|
||||
///
|
||||
/// During the visit of \a a, the returned checker stores explicitely all
|
||||
/// the traversed states.
|
||||
/// The method \a check() of the checker can be called several times
|
||||
/// (until it returns a null pointer) to enumerate all the visited acceptance
|
||||
/// paths. The implemented algorithm is the following:
|
||||
///
|
||||
/** \verbatim
|
||||
procedure check ()
|
||||
begin
|
||||
call dfs_blue(s0);
|
||||
end;
|
||||
|
||||
procedure dfs_blue (s)
|
||||
begin
|
||||
s.color = blue;
|
||||
for all t in post(s) do
|
||||
if t.color == white then
|
||||
call dfs_blue(t);
|
||||
end if;
|
||||
if (the edge (s,t) is accepting) then
|
||||
target = s;
|
||||
call dfs_red(t);
|
||||
end if;
|
||||
end for;
|
||||
end;
|
||||
|
||||
procedure dfs_red(s)
|
||||
begin
|
||||
s.color = red;
|
||||
if s == target then
|
||||
report cycle
|
||||
end if;
|
||||
for all t in post(s) do
|
||||
if t.color == blue then
|
||||
call dfs_red(t);
|
||||
end if;
|
||||
end for;
|
||||
end;
|
||||
\endverbatim */
|
||||
///
|
||||
/// This algorithm is an adaptation to TBA of the one
|
||||
/// (which deals with accepting states) presented in
|
||||
///
|
||||
/** \verbatim
|
||||
Article{ courcoubetis.92.fmsd,
|
||||
author = {Costas Courcoubetis and Moshe Y. Vardi and Pierre
|
||||
Wolper and Mihalis Yannakakis},
|
||||
title = {Memory-Efficient Algorithm for the Verification of
|
||||
Temporal Properties},
|
||||
journal = {Formal Methods in System Design},
|
||||
pages = {275--288},
|
||||
year = {1992},
|
||||
volume = {1}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \bug The name is misleading. Magic-search is the algorithm
|
||||
/// from \c godefroid.93.pstv, not \c courcoubetis.92.fmsd.
|
||||
SPOT_API emptiness_check_ptr
|
||||
explicit_magic_search(const const_twa_ptr& a,
|
||||
option_map o = option_map());
|
||||
|
||||
/// \brief Returns an emptiness checker on the spot::tgba automaton \a a.
|
||||
///
|
||||
/// \pre The automaton \a a must have at most one acceptance condition (i.e.
|
||||
/// it is a TBA).
|
||||
///
|
||||
/// During the visit of \a a, the returned checker does not store explicitely
|
||||
/// the traversed states but uses the bit-state hashing technic presented in:
|
||||
///
|
||||
/** \verbatim
|
||||
@book{Holzmann91,
|
||||
author = {G.J. Holzmann},
|
||||
title = {Design and Validation of Computer Protocols},
|
||||
publisher = {Prentice-Hall},
|
||||
address = {Englewood Cliffs, New Jersey},
|
||||
year = {1991}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// Consequently, the detection of an acceptence cycle is not ensured.
|
||||
///
|
||||
/// The size of the heap is limited to \n size bytes.
|
||||
///
|
||||
/// The implemented algorithm is the same as the one of
|
||||
/// spot::explicit_magic_search.
|
||||
///
|
||||
/// \sa spot::explicit_magic_search
|
||||
///
|
||||
SPOT_API emptiness_check_ptr
|
||||
bit_state_hashing_magic_search(const const_twa_ptr& a, size_t size,
|
||||
option_map o = option_map());
|
||||
|
||||
/// \brief Wrapper for the two magic_search implementations.
|
||||
///
|
||||
/// This wrapper calls explicit_magic_search_search() or
|
||||
/// bit_state_hashing_magic_search() according to the \c "bsh" option
|
||||
/// in the \c option_map. If \c "bsh" is set and non null, its value
|
||||
/// is used as the size of the hash map.
|
||||
SPOT_API emptiness_check_ptr
|
||||
magic_search(const const_twa_ptr& a, option_map o = option_map());
|
||||
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "mask.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
twa_graph_ptr mask_acc_sets(const const_twa_graph_ptr& in,
|
||||
acc_cond::mark_t to_remove)
|
||||
{
|
||||
auto res = make_twa_graph(in->get_dict());
|
||||
res->copy_ap_of(in);
|
||||
res->prop_copy(in, { true, true, true, false });
|
||||
unsigned na = in->num_sets();
|
||||
unsigned tr = to_remove.count();
|
||||
assert(tr <= na);
|
||||
res->set_acceptance(na - tr,
|
||||
in->get_acceptance().strip(to_remove, true));
|
||||
transform_accessible(in, res, [&](unsigned,
|
||||
bdd& cond,
|
||||
acc_cond::mark_t& acc,
|
||||
unsigned)
|
||||
{
|
||||
if (acc & to_remove)
|
||||
cond = bddfalse;
|
||||
else
|
||||
acc = acc.strip(to_remove);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr mask_keep_states(const const_twa_graph_ptr& in,
|
||||
std::vector<bool>& to_keep,
|
||||
unsigned int init)
|
||||
{
|
||||
if (to_keep.size() < in->num_states())
|
||||
to_keep.resize(in->num_states(), false);
|
||||
|
||||
auto res = make_twa_graph(in->get_dict());
|
||||
res->copy_ap_of(in);
|
||||
res->prop_copy(in, { true, true, true, false });
|
||||
res->copy_acceptance_of(in);
|
||||
transform_copy(in, res, [&](unsigned src,
|
||||
bdd& cond,
|
||||
acc_cond::mark_t&,
|
||||
unsigned dst)
|
||||
{
|
||||
if (!to_keep[src] || !to_keep[dst])
|
||||
cond = bddfalse;
|
||||
}, init);
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Clone and mask an automaton.
|
||||
///
|
||||
/// Copy the edges of automaton \a old, into automaton
|
||||
/// \a cpy, creating new states at the same time. The argument \a
|
||||
/// trans should behave as a function with the following prototype:
|
||||
/// <code>
|
||||
/// void (*trans) (unsigned srcbdd& cond, acc_cond::mark_t& acc,
|
||||
/// unsigned dst)
|
||||
/// </code>
|
||||
/// It can modify either the condition or the acceptance sets of
|
||||
/// the edges. Set the condition to bddfalse to remove the edge
|
||||
/// (this will also remove the destination state and its descendants
|
||||
/// if they are not reachable by another edge).
|
||||
/// \param init The optional new initial state.
|
||||
|
||||
template<typename Trans>
|
||||
void transform_accessible(const const_twa_graph_ptr& old,
|
||||
twa_graph_ptr& cpy,
|
||||
Trans trans, unsigned int init)
|
||||
{
|
||||
std::vector<unsigned> todo;
|
||||
std::vector<unsigned> seen(old->num_states(), -1U);
|
||||
|
||||
auto new_state =
|
||||
[&](unsigned old_state) -> unsigned
|
||||
{
|
||||
unsigned tmp = seen[old_state];
|
||||
if (tmp == -1U)
|
||||
{
|
||||
tmp = cpy->new_state();
|
||||
seen[old_state] = tmp;
|
||||
todo.push_back(old_state);
|
||||
}
|
||||
return tmp;
|
||||
};
|
||||
|
||||
cpy->set_init_state(new_state(init));
|
||||
while (!todo.empty())
|
||||
{
|
||||
unsigned old_src = todo.back();
|
||||
todo.pop_back();
|
||||
|
||||
unsigned new_src = seen[old_src];
|
||||
assert(new_src != -1U);
|
||||
|
||||
for (auto& t: old->out(old_src))
|
||||
{
|
||||
bdd cond = t.cond;
|
||||
acc_cond::mark_t acc = t.acc;
|
||||
trans(t.src, cond, acc, t.dst);
|
||||
|
||||
if (cond != bddfalse)
|
||||
cpy->new_edge(new_src,
|
||||
new_state(t.dst),
|
||||
cond, acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Copy an automaton and update each edge.
|
||||
///
|
||||
/// Copy the states of automaton \a old, into automaton
|
||||
/// \a cpy. Each state in \a cpy will have the same id as the ones in \a old.
|
||||
/// The argument \a trans
|
||||
/// should behave as a function with the following prototype:
|
||||
/// <code>
|
||||
/// void (*trans) (unsigned srcbdd& cond, acc_cond::mark_t& acc,
|
||||
/// unsigned dst)
|
||||
/// </code>
|
||||
/// It can modify either the condition or the acceptance sets of
|
||||
/// the edges. Set the condition to bddfalse to remove it. Note that
|
||||
/// all transtions will be processed.
|
||||
/// \param init The optional new initial state.
|
||||
template<typename Trans>
|
||||
void transform_copy(const const_twa_graph_ptr& old,
|
||||
twa_graph_ptr& cpy,
|
||||
Trans trans, unsigned int init)
|
||||
{
|
||||
// Each state in cpy corresponds to a unique state in old.
|
||||
cpy->new_states(old->num_states());
|
||||
cpy->set_init_state(init);
|
||||
|
||||
for (auto& t: old->edges())
|
||||
{
|
||||
bdd cond = t.cond;
|
||||
acc_cond::mark_t acc = t.acc;
|
||||
trans(t.src, cond, acc, t.dst);
|
||||
// Having the same number of states should assure that state ids are
|
||||
// equivilent in old and cpy.
|
||||
assert(t.src < cpy->num_states() && t.dst < cpy->num_states());
|
||||
if (cond != bddfalse)
|
||||
cpy->new_edge(t.src, t.dst, cond, acc);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Trans>
|
||||
void transform_accessible(const const_twa_graph_ptr& old,
|
||||
twa_graph_ptr& cpy,
|
||||
Trans trans)
|
||||
{
|
||||
transform_accessible(old, cpy, trans, old->get_init_state_number());
|
||||
}
|
||||
template<typename Trans>
|
||||
void transform_copy(const const_twa_graph_ptr& old,
|
||||
twa_graph_ptr& cpy,
|
||||
Trans trans)
|
||||
{
|
||||
transform_copy(old, cpy, trans, old->get_init_state_number());
|
||||
}
|
||||
|
||||
/// \brief Remove all edges that belong to some given acceptance sets.
|
||||
SPOT_API
|
||||
twa_graph_ptr mask_acc_sets(const const_twa_graph_ptr& in,
|
||||
acc_cond::mark_t to_remove);
|
||||
|
||||
/// \brief Keep only the states as specified by \a to_keep.
|
||||
///
|
||||
/// Each index in the vector \a to_keep specifies wether or not to keep that
|
||||
/// state. The initial state will be set to \a init.
|
||||
SPOT_API
|
||||
twa_graph_ptr mask_keep_states(const const_twa_graph_ptr& in,
|
||||
std::vector<bool>& to_keep,
|
||||
unsigned int init);
|
||||
}
|
||||
|
|
@ -1,677 +0,0 @@
|
|||
// -*- 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 <queue>
|
||||
#include <deque>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include "minimize.hh"
|
||||
#include "misc/hash.hh"
|
||||
#include "misc/bddlt.hh"
|
||||
#include "product.hh"
|
||||
#include "powerset.hh"
|
||||
#include "gtec/gtec.hh"
|
||||
#include "strength.hh"
|
||||
#include "sccfilter.hh"
|
||||
#include "sccinfo.hh"
|
||||
#include "ltl2tgba_fm.hh"
|
||||
#include "bfssteps.hh"
|
||||
#include "isdet.hh"
|
||||
#include "complement.hh"
|
||||
#include "remfin.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
// FIXME: do we really want to use unordered_set instead of set here?
|
||||
// This calls for benchmarking.
|
||||
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;
|
||||
|
||||
namespace
|
||||
{
|
||||
static std::ostream&
|
||||
dump_hash_set(const hash_set* hs,
|
||||
const const_twa_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_twa_ptr aut)
|
||||
{
|
||||
std::ostringstream s;
|
||||
dump_hash_set(hs, aut, s);
|
||||
return s.str();
|
||||
}
|
||||
|
||||
// Find all states of an automaton.
|
||||
static void
|
||||
build_state_set(const const_twa_ptr& a, hash_set* seen)
|
||||
{
|
||||
std::queue<const state*> tovisit;
|
||||
// Perform breadth-first traversal.
|
||||
const state* init = a->get_init_state();
|
||||
tovisit.push(init);
|
||||
seen->insert(init);
|
||||
while (!tovisit.empty())
|
||||
{
|
||||
const state* src = tovisit.front();
|
||||
tovisit.pop();
|
||||
|
||||
for (auto sit: a->succ(src))
|
||||
{
|
||||
const state* dst = sit->dst();
|
||||
// Is it a new state ?
|
||||
if (seen->find(dst) == seen->end())
|
||||
{
|
||||
// Register the successor for later processing.
|
||||
tovisit.push(dst);
|
||||
seen->insert(dst);
|
||||
}
|
||||
else
|
||||
dst->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From the base automaton and the list of sets, build the minimal
|
||||
// resulting automaton
|
||||
static twa_graph_ptr
|
||||
build_result(const const_twa_ptr& a,
|
||||
std::list<hash_set*>& sets,
|
||||
hash_set* final)
|
||||
{
|
||||
auto dict = a->get_dict();
|
||||
auto res = make_twa_graph(dict);
|
||||
res->copy_ap_of(a);
|
||||
res->prop_state_acc(true);
|
||||
|
||||
// For each set, create a state in the resulting 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;
|
||||
for (sit = sets.begin(); sit != sets.end(); ++sit)
|
||||
{
|
||||
hash_set::iterator hit;
|
||||
hash_set* h = *sit;
|
||||
unsigned num = res->new_state();
|
||||
for (hit = h->begin(); hit != h->end(); ++hit)
|
||||
state_num[*hit] = num;
|
||||
}
|
||||
|
||||
// For each transition in the initial automaton, add the corresponding
|
||||
// transition in res.
|
||||
|
||||
if (!final->empty())
|
||||
res->set_buchi();
|
||||
|
||||
for (sit = sets.begin(); sit != sets.end(); ++sit)
|
||||
{
|
||||
hash_set* h = *sit;
|
||||
|
||||
// Pick one state.
|
||||
const state* src = *h->begin();
|
||||
unsigned src_num = state_num[src];
|
||||
bool accepting = (final->find(src) != final->end());
|
||||
|
||||
// Connect it to all destinations.
|
||||
for (auto succit: a->succ(src))
|
||||
{
|
||||
const state* dst = succit->dst();
|
||||
hash_map::const_iterator i = state_num.find(dst);
|
||||
dst->destroy();
|
||||
if (i == state_num.end()) // Ignore useless destinations.
|
||||
continue;
|
||||
res->new_acc_edge(src_num, i->second,
|
||||
succit->cond(), accepting);
|
||||
}
|
||||
}
|
||||
res->merge_edges();
|
||||
if (res->num_states() > 0)
|
||||
{
|
||||
const state* init_state = a->get_init_state();
|
||||
unsigned init_num = state_num[init_state];
|
||||
init_state->destroy();
|
||||
res->set_init_state(init_num);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
struct wdba_search_acc_loop : public bfs_steps
|
||||
{
|
||||
wdba_search_acc_loop(const const_twa_ptr& det_a,
|
||||
unsigned scc_n, scc_info& sm,
|
||||
power_map& pm, const state* dest)
|
||||
: bfs_steps(det_a), scc_n(scc_n), sm(sm), pm(pm), dest(dest)
|
||||
{
|
||||
seen(dest);
|
||||
}
|
||||
|
||||
virtual const state*
|
||||
filter(const state* s)
|
||||
{
|
||||
s = seen(s);
|
||||
if (sm.scc_of(std::static_pointer_cast<const twa_graph>(a_)
|
||||
->state_number(s)) != scc_n)
|
||||
return nullptr;
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
match(twa_run::step&, const state* to)
|
||||
{
|
||||
return to == dest;
|
||||
}
|
||||
|
||||
unsigned scc_n;
|
||||
scc_info& sm;
|
||||
power_map& pm;
|
||||
const state* dest;
|
||||
state_unicity_table seen;
|
||||
};
|
||||
|
||||
|
||||
bool
|
||||
wdba_scc_is_accepting(const const_twa_graph_ptr& det_a, unsigned scc_n,
|
||||
const const_twa_graph_ptr& orig_a, scc_info& sm,
|
||||
power_map& pm)
|
||||
{
|
||||
// Get some state from the SCC #n.
|
||||
const state* start = det_a->state_from_number(sm.one_state_of(scc_n));
|
||||
|
||||
// Find a loop around START in SCC #n.
|
||||
wdba_search_acc_loop wsal(det_a, scc_n, sm, pm, start);
|
||||
twa_run::steps loop;
|
||||
const state* reached = wsal.search(start, loop);
|
||||
assert(reached == start);
|
||||
(void)reached;
|
||||
|
||||
// Build an automaton representing this loop.
|
||||
auto loop_a = make_twa_graph(det_a->get_dict());
|
||||
twa_run::steps::const_iterator i;
|
||||
int loop_size = loop.size();
|
||||
loop_a->new_states(loop_size);
|
||||
int n;
|
||||
for (n = 1, i = loop.begin(); n < loop_size; ++n, ++i)
|
||||
{
|
||||
loop_a->new_edge(n - 1, n, i->label);
|
||||
i->s->destroy();
|
||||
}
|
||||
assert(i != loop.end());
|
||||
loop_a->new_edge(n - 1, 0, i->label);
|
||||
i->s->destroy();
|
||||
assert(++i == loop.end());
|
||||
|
||||
loop_a->set_init_state(0U);
|
||||
|
||||
// Check if the loop is accepting in the original automaton.
|
||||
bool accepting = false;
|
||||
|
||||
// Iterate on each original state corresponding to start.
|
||||
const power_map::power_state& ps =
|
||||
pm.states_of(det_a->state_number(start));
|
||||
for (auto& s: ps)
|
||||
{
|
||||
// Construct a product between LOOP_A and ORIG_A starting in
|
||||
// S. FIXME: This could be sped up a lot!
|
||||
if (!product(loop_a, orig_a, 0U, s)->is_empty())
|
||||
{
|
||||
accepting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return accepting;
|
||||
}
|
||||
|
||||
static twa_graph_ptr minimize_dfa(const const_twa_graph_ptr& det_a,
|
||||
hash_set* final, hash_set* non_final)
|
||||
{
|
||||
typedef std::list<hash_set*> partition_t;
|
||||
partition_t cur_run;
|
||||
partition_t next_run;
|
||||
|
||||
// The list of equivalent states.
|
||||
partition_t done;
|
||||
|
||||
hash_map state_set_map;
|
||||
|
||||
// Size of det_a
|
||||
unsigned size = final->size() + non_final->size();
|
||||
// Use bdd variables to number sets. set_num is the first variable
|
||||
// available.
|
||||
unsigned set_num =
|
||||
det_a->get_dict()->register_anonymous_variables(size, det_a);
|
||||
|
||||
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;
|
||||
|
||||
hash_set* final_copy;
|
||||
|
||||
if (!final->empty())
|
||||
{
|
||||
unsigned s = final->size();
|
||||
used_var[set_num] = s;
|
||||
free_var.erase(set_num);
|
||||
if (s > 1)
|
||||
cur_run.push_back(final);
|
||||
else
|
||||
done.push_back(final);
|
||||
for (hash_set::const_iterator i = final->begin();
|
||||
i != final->end(); ++i)
|
||||
state_set_map[*i] = set_num;
|
||||
|
||||
final_copy = new hash_set(*final);
|
||||
}
|
||||
else
|
||||
{
|
||||
final_copy = final;
|
||||
}
|
||||
|
||||
if (!non_final->empty())
|
||||
{
|
||||
unsigned s = non_final->size();
|
||||
unsigned num = set_num + 1;
|
||||
used_var[num] = s;
|
||||
free_var.erase(num);
|
||||
if (s > 1)
|
||||
cur_run.push_back(non_final);
|
||||
else
|
||||
done.push_back(non_final);
|
||||
for (hash_set::const_iterator i = non_final->begin();
|
||||
i != non_final->end(); ++i)
|
||||
state_set_map[*i] = num;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete non_final;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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, det_a)
|
||||
<< 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;
|
||||
for (auto si: det_a->succ(src))
|
||||
{
|
||||
const state* dst = si->dst();
|
||||
hash_map::const_iterator i = state_set_map.find(dst);
|
||||
dst->destroy();
|
||||
if (i == state_set_map.end())
|
||||
// The destination state is not in our
|
||||
// partition. This can happen if the initial
|
||||
// FINAL and NON_FINAL supplied to the algorithm
|
||||
// do not cover the whole automaton (because we
|
||||
// want to ignore some useless states). Simply
|
||||
// ignore these states here.
|
||||
continue;
|
||||
f |= (bdd_ithvar(i->second) & si->cond());
|
||||
}
|
||||
|
||||
// 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, det_a)
|
||||
<< " 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, det_a)
|
||||
<< " is minimal" << std::endl;
|
||||
done.push_back(set);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << "set " << format_hash_set(set, det_a)
|
||||
<< " 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;
|
||||
else
|
||||
trace << "splitting did not 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, det_a) << ' ';
|
||||
trace << std::endl;
|
||||
#endif
|
||||
|
||||
// Build the result.
|
||||
auto res = build_result(det_a, done, final_copy);
|
||||
|
||||
// Free all the allocated memory.
|
||||
delete final_copy;
|
||||
hash_map::iterator hit;
|
||||
for (hit = state_set_map.begin(); hit != state_set_map.end();)
|
||||
{
|
||||
hash_map::iterator old = hit++;
|
||||
old->first->destroy();
|
||||
}
|
||||
std::list<hash_set*>::iterator it;
|
||||
for (it = done.begin(); it != done.end(); ++it)
|
||||
delete *it;
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr minimize_monitor(const const_twa_graph_ptr& a)
|
||||
{
|
||||
hash_set* final = new hash_set;
|
||||
hash_set* non_final = new hash_set;
|
||||
twa_graph_ptr det_a = tgba_powerset(a);
|
||||
|
||||
// non_final contain all states.
|
||||
// final is empty: there is no acceptance condition
|
||||
build_state_set(det_a, non_final);
|
||||
auto res = minimize_dfa(det_a, final, non_final);
|
||||
res->prop_copy(a, { false, false, false, true });
|
||||
res->prop_deterministic(true);
|
||||
res->prop_weak(true);
|
||||
res->prop_state_acc(true);
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr minimize_wdba(const const_twa_graph_ptr& a)
|
||||
{
|
||||
hash_set* final = new hash_set;
|
||||
hash_set* non_final = new hash_set;
|
||||
|
||||
twa_graph_ptr det_a;
|
||||
|
||||
{
|
||||
power_map pm;
|
||||
det_a = tgba_powerset(a, pm);
|
||||
|
||||
// For each SCC of the deterministic automaton, determine if it
|
||||
// is accepting or not.
|
||||
|
||||
// This corresponds to the algorithm in Fig. 1 of "Efficient
|
||||
// minimization of deterministic weak omega-automata" written by
|
||||
// Christof Löding and published in Information Processing
|
||||
// Letters 79 (2001) pp 105--109.
|
||||
|
||||
// We also keep track of whether an SCC is useless
|
||||
// (i.e., it is not the start of any accepting word).
|
||||
|
||||
scc_info sm(det_a);
|
||||
sm.determine_unknown_acceptance();
|
||||
unsigned scc_count = sm.scc_count();
|
||||
// SCC that have been marked as useless.
|
||||
std::vector<bool> useless(scc_count);
|
||||
// The "color". Even number correspond to
|
||||
// accepting SCCs.
|
||||
std::vector<unsigned> d(scc_count);
|
||||
|
||||
// An even number larger than scc_count.
|
||||
unsigned k = (scc_count | 1) + 1;
|
||||
|
||||
// SCC are numbered in topological order
|
||||
// (but in the reverse order as Löding's)
|
||||
for (unsigned m = 0; m < scc_count; ++m)
|
||||
{
|
||||
bool is_useless = true;
|
||||
bool transient = sm.is_trivial(m);
|
||||
auto& succ = sm.succ(m);
|
||||
|
||||
if (transient && succ.empty())
|
||||
{
|
||||
// A trivial SCC without successor is useless.
|
||||
useless[m] = true;
|
||||
d[m] = k - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute the minimum color l of the successors.
|
||||
// Also SCCs are useless if all their successor are
|
||||
// useless.
|
||||
unsigned l = k;
|
||||
for (unsigned j: succ)
|
||||
{
|
||||
is_useless &= useless[j];
|
||||
unsigned dj = d[j];
|
||||
if (dj < l)
|
||||
l = dj;
|
||||
}
|
||||
|
||||
if (transient)
|
||||
{
|
||||
d[m] = l;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular SCCs are accepting if any of their loop
|
||||
// corresponds to an accepted word in the original
|
||||
// automaton.
|
||||
if (wdba_scc_is_accepting(det_a, m, a, sm, pm))
|
||||
{
|
||||
is_useless = false;
|
||||
d[m] = l & ~1; // largest even number inferior or equal
|
||||
}
|
||||
else
|
||||
{
|
||||
d[m] = (l - 1) | 1; // largest odd number inferior or equal
|
||||
}
|
||||
}
|
||||
|
||||
useless[m] = is_useless;
|
||||
|
||||
if (!is_useless)
|
||||
{
|
||||
hash_set* dest_set = (d[m] & 1) ? non_final : final;
|
||||
for (auto s: sm.states_of(m))
|
||||
dest_set->insert(det_a->state_from_number(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto res = minimize_dfa(det_a, final, non_final);
|
||||
res->prop_copy(a, { false, false, false, true });
|
||||
res->prop_deterministic(true);
|
||||
res->prop_weak(true);
|
||||
// If the input was terminal, then the output is also terminal.
|
||||
// FIXME:
|
||||
// (1) We should have a specialized version of this function for
|
||||
// the case where the input is terminal. See issue #120.
|
||||
// (2) It would be nice to have a more precise detection of
|
||||
// terminal automata in the output. Calling
|
||||
// is_terminal_automaton() seems overkill here. But maybe we can
|
||||
// add a quick check inside minimize_dfa.
|
||||
if (a->prop_terminal())
|
||||
res->prop_terminal(true);
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
minimize_obligation(const const_twa_graph_ptr& aut_f,
|
||||
formula f,
|
||||
const_twa_graph_ptr aut_neg_f,
|
||||
bool reject_bigger)
|
||||
{
|
||||
auto min_aut_f = minimize_wdba(aut_f);
|
||||
|
||||
if (reject_bigger)
|
||||
{
|
||||
// Abort if min_aut_f has more states than aut_f.
|
||||
unsigned orig_states = aut_f->num_states();
|
||||
if (orig_states < min_aut_f->num_states())
|
||||
return std::const_pointer_cast<twa_graph>(aut_f);
|
||||
}
|
||||
|
||||
// If the input automaton was already weak and deterministic, the
|
||||
// output is necessary correct.
|
||||
if (aut_f->prop_weak() && aut_f->prop_deterministic())
|
||||
return min_aut_f;
|
||||
|
||||
// if f is a syntactic obligation formula, the WDBA minimization
|
||||
// must be correct.
|
||||
if (f && f.is_syntactic_obligation())
|
||||
return min_aut_f;
|
||||
|
||||
// If aut_f is a guarantee automaton, the WDBA minimization must be
|
||||
// correct.
|
||||
if (is_terminal_automaton(aut_f))
|
||||
return min_aut_f;
|
||||
|
||||
// Build negation automaton if not supplied.
|
||||
if (!aut_neg_f)
|
||||
{
|
||||
if (f)
|
||||
{
|
||||
// If we know the formula, simply build the automaton for
|
||||
// its negation.
|
||||
aut_neg_f = ltl_to_tgba_fm(formula::Not(f), aut_f->get_dict());
|
||||
// Remove useless SCCs.
|
||||
aut_neg_f = scc_filter(aut_neg_f, true);
|
||||
}
|
||||
else if (is_deterministic(aut_f))
|
||||
{
|
||||
// If the automaton is deterministic, complementing is
|
||||
// easy.
|
||||
aut_neg_f = remove_fin(dtwa_complement(aut_f));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, we cannot check if the minimization is safe.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// If the negation is a guarantee automaton, then the
|
||||
// minimization is correct.
|
||||
if (is_terminal_automaton(aut_neg_f))
|
||||
return min_aut_f;
|
||||
|
||||
bool ok = false;
|
||||
|
||||
if (product(min_aut_f, aut_neg_f)->is_empty())
|
||||
{
|
||||
// Complement the minimized WDBA.
|
||||
assert(min_aut_f->prop_weak());
|
||||
auto neg_min_aut_f = remove_fin(dtwa_complement(min_aut_f));
|
||||
if (product(aut_f, neg_min_aut_f)->is_empty())
|
||||
// Finally, we are now sure that it was safe
|
||||
// to minimize the automaton.
|
||||
ok = true;
|
||||
}
|
||||
|
||||
if (ok)
|
||||
return min_aut_f;
|
||||
return std::const_pointer_cast<twa_graph>(aut_f);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "twa/twagraph.hh"
|
||||
#include "tl/formula.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup twa_reduction
|
||||
/// @{
|
||||
|
||||
/// \brief Construct a minimal deterministic monitor.
|
||||
///
|
||||
/// The automaton will be converted into minimal deterministic
|
||||
/// monitor. All useless SCCs should have been previously removed
|
||||
/// (using scc_filter() for instance). Then the automaton will be
|
||||
/// determinized and minimized using the standard DFA construction
|
||||
/// as if all states were accepting states.
|
||||
///
|
||||
/// For more detail about monitors, see the following paper:
|
||||
/** \verbatim
|
||||
@InProceedings{ tabakov.10.rv,
|
||||
author = {Deian Tabakov and Moshe Y. Vardi},
|
||||
title = {Optimized Temporal Monitors for SystemC{$^*$}},
|
||||
booktitle = {Proceedings of the 10th International Conferance
|
||||
on Runtime Verification},
|
||||
pages = {436--451},
|
||||
year = 2010,
|
||||
volume = {6418},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
month = nov,
|
||||
publisher = {Spring-Verlag}
|
||||
}
|
||||
\endverbatim */
|
||||
/// (Note: although the above paper uses Spot, this function did not
|
||||
/// exist in Spot at that time.)
|
||||
///
|
||||
/// \param a the automaton to convert into a minimal deterministic monitor
|
||||
/// \pre Dead SCCs should have been removed from \a a before
|
||||
/// calling this function.
|
||||
SPOT_API twa_graph_ptr minimize_monitor(const const_twa_graph_ptr& a);
|
||||
|
||||
/// \brief Minimize a Büchi automaton in the WDBA class.
|
||||
///
|
||||
/// This takes a TGBA whose language is representable by a Weak
|
||||
/// Deterministic Büchi Automaton, and construct a minimal WDBA for
|
||||
/// this language. This essentially chains three algorithms:
|
||||
/// determinization, acceptance adjustment (Löding's coloring
|
||||
/// algorithm), and minimization (using a Moore-like approache).
|
||||
///
|
||||
/// If the input automaton does not represent a WDBA language,
|
||||
/// the resulting automaton is still a WDBA, but it will not
|
||||
/// be equivalent to the original automaton. Use the
|
||||
/// minimize_obligation() function if you are not sure whether
|
||||
/// it is safe to call this function.
|
||||
///
|
||||
/// Please see the following paper for a discussion of this
|
||||
/// technique.
|
||||
///
|
||||
/** \verbatim
|
||||
@InProceedings{ dax.07.atva,
|
||||
author = {Christian Dax and Jochen Eisinger and Felix Klaedtke},
|
||||
title = {Mechanizing the Powerset Construction for Restricted
|
||||
Classes of {$\omega$}-Automata},
|
||||
year = 2007,
|
||||
series = {Lecture Notes in Computer Science},
|
||||
publisher = {Springer-Verlag},
|
||||
volume = 4762,
|
||||
booktitle = {Proceedings of the 5th International Symposium on
|
||||
Automated Technology for Verification and Analysis
|
||||
(ATVA'07)},
|
||||
editor = {Kedar S. Namjoshi and Tomohiro Yoneda and Teruo Higashino
|
||||
and Yoshio Okamura},
|
||||
month = oct
|
||||
}
|
||||
\endverbatim */
|
||||
SPOT_API twa_graph_ptr minimize_wdba(const const_twa_graph_ptr& a);
|
||||
|
||||
/// \brief Minimize an automaton if it represents an obligation property.
|
||||
///
|
||||
/// This function attempts to minimize the automaton \a aut_f using the
|
||||
/// algorithm implemented in the minimize_wdba() function, and presented
|
||||
/// by the following paper:
|
||||
///
|
||||
/** \verbatim
|
||||
@InProceedings{ dax.07.atva,
|
||||
author = {Christian Dax and Jochen Eisinger and Felix Klaedtke},
|
||||
title = {Mechanizing the Powerset Construction for Restricted
|
||||
Classes of {$\omega$}-Automata},
|
||||
year = 2007,
|
||||
series = {Lecture Notes in Computer Science},
|
||||
publisher = {Springer-Verlag},
|
||||
volume = 4762,
|
||||
booktitle = {Proceedings of the 5th International Symposium on
|
||||
Automated Technology for Verification and Analysis
|
||||
(ATVA'07)},
|
||||
editor = {Kedar S. Namjoshi and Tomohiro Yoneda and Teruo Higashino
|
||||
and Yoshio Okamura},
|
||||
month = oct
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// Because it is hard to determine if an automaton corresponds
|
||||
/// to an obligation property, you should supply either the formula
|
||||
/// \a f expressed by the automaton \a aut_f, or \a aut_neg_f the negation
|
||||
/// of the automaton \a aut_neg_f.
|
||||
///
|
||||
/// \param aut_f the automaton to minimize
|
||||
/// \param f the LTL formula represented by the automaton \a aut_f
|
||||
/// \param aut_neg_f an automaton representing the negation of \a aut_f
|
||||
/// \param reject_bigger Whether the minimal WDBA should be discarded if
|
||||
/// it has more states than the input.
|
||||
/// \return a new tgba if the automaton could be minimized, \a aut_f if
|
||||
/// the automaton cannot be minimized, 0 if we do not know if the
|
||||
/// minimization is correct because neither \a f nor \a aut_neg_f
|
||||
/// were supplied.
|
||||
///
|
||||
/// The function proceeds as follows. If the formula \a f or the
|
||||
/// automaton \a aut can easily be proved to represent an obligation
|
||||
/// formula, then the result of <code>minimize(aut)</code> is
|
||||
/// returned. Otherwise, if \a aut_neg_f was not supplied but \a f
|
||||
/// was, \a aut_neg_f is built from the negation of \a f. Then we
|
||||
/// check that <code>product(aut,!minimize(aut_f))</code> and <code>
|
||||
/// product(aut_neg_f,minize(aut))</code> are both empty. If they
|
||||
/// are, the the minimization was sound. (See the paper for full
|
||||
/// details.)
|
||||
///
|
||||
/// If \a reject_bigger is set, this function will return the input
|
||||
/// automaton \a aut_f when the minimized WDBA has more states than
|
||||
/// the input automaton. (More states are possible because of
|
||||
/// determinization step during minimize_wdba().) Note that
|
||||
/// checking the size of the minimized WDBA occurs before ensuring
|
||||
/// that the minimized WDBA is correct.
|
||||
SPOT_API twa_graph_ptr
|
||||
minimize_obligation(const const_twa_graph_ptr& aut_f,
|
||||
formula f = nullptr,
|
||||
const_twa_graph_ptr aut_neg_f = nullptr,
|
||||
bool reject_bigger = false);
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,672 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014, 2015 Laboratoire de recherche et
|
||||
// développement de l'Epita (LRDE).
|
||||
// Copyright (C) 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
|
||||
|
||||
//#define NDFSR_TRACE
|
||||
|
||||
#include <iostream>
|
||||
#ifdef NDFSR_TRACE
|
||||
#define ndfsr_trace std::cerr
|
||||
#else
|
||||
#define ndfsr_trace while (0) std::cerr
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
#include "misc/hash.hh"
|
||||
#include "twa/twa.hh"
|
||||
#include "emptiness.hh"
|
||||
#include "emptiness_stats.hh"
|
||||
#include "bfssteps.hh"
|
||||
#include "misc/hash.hh"
|
||||
|
||||
|
||||
namespace spot
|
||||
{
|
||||
struct stack_item
|
||||
{
|
||||
stack_item(const state* n, twa_succ_iterator* i, bdd l, acc_cond::mark_t a)
|
||||
: s(n), it(i), label(l), acc(a) {};
|
||||
/// The visited state.
|
||||
const state* s;
|
||||
/// Design the next successor of \a s which has to be visited.
|
||||
twa_succ_iterator* it;
|
||||
/// The label of the transition traversed to reach \a s
|
||||
/// (false for the first one).
|
||||
bdd label;
|
||||
/// The acceptance set of the transition traversed to reach \a s
|
||||
/// (false for the first one).
|
||||
acc_cond::mark_t acc;
|
||||
};
|
||||
|
||||
typedef std::list<stack_item> stack_type;
|
||||
|
||||
namespace
|
||||
{
|
||||
// The acss_statistics is available only when the heap has a
|
||||
// size() method (which we indicate using n==1).
|
||||
|
||||
template <typename T, int n>
|
||||
struct stats_interface
|
||||
: public ars_statistics
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct stats_interface<T, 1>
|
||||
: public acss_statistics
|
||||
{
|
||||
unsigned
|
||||
acss_states() const
|
||||
{
|
||||
// all visited states are in the state space search
|
||||
return static_cast<const T*>(this)->h_.size();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
template <typename ndfs_search, typename heap>
|
||||
class ndfs_result:
|
||||
public emptiness_check_result,
|
||||
// Conditionally inherit from acss_statistics or ars_statistics.
|
||||
public stats_interface<ndfs_result<ndfs_search, heap>, heap::Has_Size>
|
||||
{
|
||||
public:
|
||||
ndfs_result(const std::shared_ptr<ndfs_search>& ms)
|
||||
: emptiness_check_result(ms->automaton()), ms_(ms),
|
||||
h_(ms->get_heap())
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~ndfs_result()
|
||||
{
|
||||
}
|
||||
|
||||
virtual twa_run_ptr accepting_run()
|
||||
{
|
||||
const stack_type& stb = ms_->get_st_blue();
|
||||
const stack_type& str = ms_->get_st_red();
|
||||
|
||||
assert(!stb.empty());
|
||||
|
||||
acc_cond::mark_t covered_acc = 0U;
|
||||
accepting_transitions_list acc_trans;
|
||||
|
||||
const state* start;
|
||||
|
||||
start = stb.front().s->clone();
|
||||
if (!str.empty())
|
||||
{
|
||||
if (a_->num_sets() == 0)
|
||||
{
|
||||
// take arbitrarily the last transition on the red stack
|
||||
stack_type::const_iterator i, j;
|
||||
i = j = str.begin(); ++i;
|
||||
if (i == str.end())
|
||||
i = stb.begin();
|
||||
transition t = { i->s->clone(), j->label, j->acc,
|
||||
j->s->clone() };
|
||||
assert(h_.has_been_visited(t.source));
|
||||
assert(h_.has_been_visited(t.dest));
|
||||
acc_trans.push_back(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore the prefix
|
||||
stack_type::const_reverse_iterator i, j;
|
||||
|
||||
i = j = stb.rbegin(); ++j;
|
||||
while (i->s->compare(start) != 0)
|
||||
++i, ++j;
|
||||
|
||||
stack_type::const_reverse_iterator end = stb.rend();
|
||||
for (; j != end; ++i, ++j)
|
||||
{
|
||||
if ((covered_acc & j->acc) != j->acc)
|
||||
{
|
||||
transition t = { i->s->clone(), j->label, j->acc,
|
||||
j->s->clone() };
|
||||
assert(h_.has_been_visited(t.source));
|
||||
assert(h_.has_been_visited(t.dest));
|
||||
acc_trans.push_back(t);
|
||||
covered_acc |= j->acc;
|
||||
}
|
||||
}
|
||||
|
||||
j = str.rbegin();
|
||||
if ((covered_acc & j->acc) != j->acc)
|
||||
{
|
||||
transition t = { i->s->clone(), j->label, j->acc,
|
||||
j->s->clone() };
|
||||
assert(h_.has_been_visited(t.source));
|
||||
assert(h_.has_been_visited(t.dest));
|
||||
acc_trans.push_back(t);
|
||||
covered_acc |= j->acc;
|
||||
}
|
||||
|
||||
i = j; ++j;
|
||||
end = str.rend();
|
||||
for (; j != end; ++i, ++j)
|
||||
{
|
||||
if ((covered_acc & j->acc) != j->acc)
|
||||
{
|
||||
transition t = { i->s->clone(), j->label, j->acc,
|
||||
j->s->clone() };
|
||||
assert(h_.has_been_visited(t.source));
|
||||
assert(h_.has_been_visited(t.dest));
|
||||
acc_trans.push_back(t);
|
||||
covered_acc |= j->acc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!a_->acc().accepting(covered_acc))
|
||||
{
|
||||
bool b = dfs(start, acc_trans, covered_acc);
|
||||
assert(b);
|
||||
(void) b;
|
||||
}
|
||||
|
||||
start->destroy();
|
||||
|
||||
assert(!acc_trans.empty());
|
||||
|
||||
auto run = std::make_shared<twa_run>(automaton());
|
||||
// construct run->cycle from acc_trans.
|
||||
construct_cycle(run, acc_trans);
|
||||
// construct run->prefix (a minimal path from the initial state to any
|
||||
// state of run->cycle) and adjust the cycle to the state reached by the
|
||||
// prefix.
|
||||
construct_prefix(run);
|
||||
|
||||
for (typename accepting_transitions_list::const_iterator i =
|
||||
acc_trans.begin(); i != acc_trans.end(); ++i)
|
||||
{
|
||||
i->source->destroy();
|
||||
i->dest->destroy();
|
||||
}
|
||||
|
||||
return run;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<ndfs_search> ms_;
|
||||
const heap& h_;
|
||||
template <typename T, int n>
|
||||
friend struct stats_interface;
|
||||
|
||||
struct transition {
|
||||
const state* source;
|
||||
bdd label;
|
||||
acc_cond::mark_t acc;
|
||||
const state* dest;
|
||||
};
|
||||
typedef std::list<transition> accepting_transitions_list;
|
||||
|
||||
typedef std::unordered_set<const state*,
|
||||
state_ptr_hash, state_ptr_equal> state_set;
|
||||
|
||||
void clean(const const_twa_ptr& a, stack_type& st1,
|
||||
state_set& seen, state_set& dead)
|
||||
{
|
||||
while (!st1.empty())
|
||||
{
|
||||
a->release_iter(st1.front().it);
|
||||
st1.pop_front();
|
||||
}
|
||||
for (state_set::iterator i = seen.begin(); i != seen.end();)
|
||||
{
|
||||
const state* s = *i;
|
||||
++i;
|
||||
s->destroy();
|
||||
}
|
||||
for (state_set::iterator i = dead.begin(); i != dead.end();)
|
||||
{
|
||||
const state* s = *i;
|
||||
++i;
|
||||
s->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
bool dfs(const state* target, accepting_transitions_list& acc_trans,
|
||||
acc_cond::mark_t& covered_acc)
|
||||
{
|
||||
assert(h_.has_been_visited(target));
|
||||
stack_type st1;
|
||||
|
||||
state_set seen, dead;
|
||||
const state* start = target->clone();
|
||||
|
||||
seen.insert(start);
|
||||
twa_succ_iterator* i = a_->succ_iter(start);
|
||||
i->first();
|
||||
st1.emplace_front(start, i, bddfalse, 0U);
|
||||
|
||||
while (!st1.empty())
|
||||
{
|
||||
stack_item& f = st1.front();
|
||||
ndfsr_trace << "DFS1 treats: " << a_->format_state(f.s)
|
||||
<< std::endl;
|
||||
if (!f.it->done())
|
||||
{
|
||||
const state *s_prime = f.it->dst();
|
||||
ndfsr_trace << " Visit the successor: "
|
||||
<< a_->format_state(s_prime) << std::endl;
|
||||
bdd label = f.it->cond();
|
||||
auto acc = f.it->acc();
|
||||
f.it->next();
|
||||
if (h_.has_been_visited(s_prime))
|
||||
{
|
||||
if (dead.find(s_prime) != dead.end())
|
||||
{
|
||||
ndfsr_trace << " it is dead, pop it" << std::endl;
|
||||
s_prime->destroy();
|
||||
}
|
||||
else if (seen.find(s_prime) == seen.end())
|
||||
{
|
||||
this->inc_ars_cycle_states();
|
||||
ndfsr_trace << " it is not seen, go down" << std::endl;
|
||||
seen.insert(s_prime);
|
||||
twa_succ_iterator* i = a_->succ_iter(s_prime);
|
||||
i->first();
|
||||
st1.emplace_front(s_prime, i, label, acc);
|
||||
}
|
||||
else if ((acc & covered_acc) != acc)
|
||||
{
|
||||
this->inc_ars_cycle_states();
|
||||
ndfsr_trace << " a propagation is needed, "
|
||||
<< "start a search" << std::endl;
|
||||
if (search(s_prime, target, dead))
|
||||
{
|
||||
transition t = { f.s->clone(), label, acc,
|
||||
s_prime->clone() };
|
||||
assert(h_.has_been_visited(t.source));
|
||||
assert(h_.has_been_visited(t.dest));
|
||||
acc_trans.push_back(t);
|
||||
covered_acc |= acc;
|
||||
if (a_->acc().accepting(covered_acc))
|
||||
{
|
||||
clean(a_, st1, seen, dead);
|
||||
s_prime->destroy();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
s_prime->destroy();
|
||||
}
|
||||
else
|
||||
{
|
||||
ndfsr_trace << " already seen, pop it" << std::endl;
|
||||
s_prime->destroy();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ndfsr_trace << " not seen during the search, pop it"
|
||||
<< std::endl;
|
||||
s_prime->destroy();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ndfsr_trace << " all the successors have been visited"
|
||||
<< std::endl;
|
||||
stack_item f_dest(f);
|
||||
a_->release_iter(st1.front().it);
|
||||
st1.pop_front();
|
||||
if (!st1.empty() && ((f_dest.acc & covered_acc) != f_dest.acc))
|
||||
{
|
||||
ndfsr_trace << " a propagation is needed, start a search"
|
||||
<< std::endl;
|
||||
if (search(f_dest.s, target, dead))
|
||||
{
|
||||
transition t = { st1.front().s->clone(),
|
||||
f_dest.label, f_dest.acc,
|
||||
f_dest.s->clone() };
|
||||
assert(h_.has_been_visited(t.source));
|
||||
assert(h_.has_been_visited(t.dest));
|
||||
acc_trans.push_back(t);
|
||||
covered_acc |= f_dest.acc;
|
||||
if (a_->acc().accepting(covered_acc))
|
||||
{
|
||||
clean(a_, st1, seen, dead);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ndfsr_trace << " no propagation needed, pop it"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clean(a_, st1, seen, dead);
|
||||
return false;
|
||||
}
|
||||
|
||||
class test_path: public bfs_steps
|
||||
{
|
||||
public:
|
||||
test_path(ars_statistics* ars,
|
||||
const const_twa_ptr& a, const state* t,
|
||||
const state_set& d, const heap& h)
|
||||
: bfs_steps(a), ars(ars), target(t), dead(d), h(h)
|
||||
{
|
||||
}
|
||||
|
||||
~test_path()
|
||||
{
|
||||
state_set::const_iterator i = seen.begin();
|
||||
while (i != seen.end())
|
||||
{
|
||||
const state* ptr = *i;
|
||||
++i;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
const state* search(const state* start, twa_run::steps& l)
|
||||
{
|
||||
const state* s = filter(start);
|
||||
if (s)
|
||||
return this->bfs_steps::search(s, l);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const state* filter(const state* s)
|
||||
{
|
||||
if (!h.has_been_visited(s)
|
||||
|| seen.find(s) != seen.end()
|
||||
|| dead.find(s) != dead.end())
|
||||
{
|
||||
s->destroy();
|
||||
return nullptr;
|
||||
}
|
||||
ars->inc_ars_cycle_states();
|
||||
seen.insert(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
void finalize(const std::map<const state*, twa_run::step,
|
||||
state_ptr_less_than>&,
|
||||
const twa_run::step&, const state*, twa_run::steps&)
|
||||
{
|
||||
}
|
||||
|
||||
const state_set& get_seen() const
|
||||
{
|
||||
return seen;
|
||||
}
|
||||
|
||||
bool match(twa_run::step&, const state* dest)
|
||||
{
|
||||
return target->compare(dest) == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
ars_statistics* ars;
|
||||
state_set seen;
|
||||
const state* target;
|
||||
const state_set& dead;
|
||||
const heap& h;
|
||||
};
|
||||
|
||||
bool search(const state* start, const state* target, state_set& dead)
|
||||
{
|
||||
twa_run::steps path;
|
||||
if (start->compare(target) == 0)
|
||||
return true;
|
||||
|
||||
test_path s(this, a_, target, dead, h_);
|
||||
const state* res = s.search(start->clone(), path);
|
||||
if (res)
|
||||
{
|
||||
assert(res->compare(target) == 0);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_set::const_iterator it;
|
||||
for (it = s.get_seen().begin(); it != s.get_seen().end(); ++it)
|
||||
dead.insert((*it)->clone());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::unordered_multimap<const state*, transition,
|
||||
state_ptr_hash,
|
||||
state_ptr_equal> m_source_trans;
|
||||
|
||||
template<bool cycle>
|
||||
class min_path: public bfs_steps
|
||||
{
|
||||
public:
|
||||
min_path(ars_statistics* ars,
|
||||
const const_twa_ptr& a,
|
||||
const m_source_trans& target, const heap& h)
|
||||
: bfs_steps(a), ars(ars), target(target), h(h)
|
||||
{
|
||||
}
|
||||
|
||||
~min_path()
|
||||
{
|
||||
state_set::const_iterator i = seen.begin();
|
||||
while (i != seen.end())
|
||||
{
|
||||
const state* ptr = *i;
|
||||
++i;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
const state* search(const state* start, twa_run::steps& l)
|
||||
{
|
||||
const state* s = filter(start);
|
||||
if (s)
|
||||
return this->bfs_steps::search(s, l);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const state* filter(const state* s)
|
||||
{
|
||||
ndfsr_trace << "filter: " << a_->format_state(s);
|
||||
if (!h.has_been_visited(s) || seen.find(s) != seen.end())
|
||||
{
|
||||
if (!h.has_been_visited(s))
|
||||
ndfsr_trace << " not visited" << std::endl;
|
||||
else
|
||||
ndfsr_trace << " already seen" << std::endl;
|
||||
s->destroy();
|
||||
return nullptr;
|
||||
}
|
||||
ndfsr_trace << " OK" << std::endl;
|
||||
if (cycle)
|
||||
ars->inc_ars_cycle_states();
|
||||
else
|
||||
ars->inc_ars_prefix_states();
|
||||
seen.insert(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool match(twa_run::step&, const state* dest)
|
||||
{
|
||||
ndfsr_trace << "match: " << a_->format_state(dest)
|
||||
<< std::endl;
|
||||
return target.find(dest) != target.end();
|
||||
}
|
||||
|
||||
private:
|
||||
ars_statistics* ars;
|
||||
state_set seen;
|
||||
const m_source_trans& target;
|
||||
const heap& h;
|
||||
};
|
||||
|
||||
void construct_cycle(twa_run_ptr run,
|
||||
const accepting_transitions_list& acc_trans)
|
||||
{
|
||||
assert(!acc_trans.empty());
|
||||
transition current = acc_trans.front();
|
||||
// insert the first accepting transition in the cycle
|
||||
ndfsr_trace << "the initial accepting transition is from "
|
||||
<< a_->format_state(current.source) << " to "
|
||||
<< a_->format_state(current.dest) << std::endl;
|
||||
const state* begin = current.source;
|
||||
|
||||
m_source_trans target;
|
||||
typename accepting_transitions_list::const_iterator i =
|
||||
acc_trans.begin();
|
||||
ndfsr_trace << "targets are the source states: ";
|
||||
for (++i; i != acc_trans.end(); ++i)
|
||||
{
|
||||
if (i->source->compare(begin) == 0 &&
|
||||
i->source->compare(i->dest) == 0)
|
||||
{
|
||||
ndfsr_trace << "(self loop " << a_->format_state(i->source)
|
||||
<< " -> " << a_->format_state(i->dest)
|
||||
<< " ignored) ";
|
||||
twa_run::step st = { i->source->clone(), i->label, i->acc };
|
||||
run->cycle.push_back(st);
|
||||
}
|
||||
else
|
||||
{
|
||||
ndfsr_trace << a_->format_state(i->source) << " (-> "
|
||||
<< a_->format_state(i->dest) << ") ";
|
||||
target.emplace(i->source, *i);
|
||||
}
|
||||
}
|
||||
ndfsr_trace << std::endl;
|
||||
|
||||
twa_run::step st = { current.source->clone(), current.label,
|
||||
current.acc };
|
||||
run->cycle.push_back(st);
|
||||
|
||||
while (!target.empty())
|
||||
{
|
||||
// find a minimal path from current.dest to any source state in
|
||||
// target.
|
||||
ndfsr_trace << "looking for a path from "
|
||||
<< a_->format_state(current.dest) << std::endl;
|
||||
typename m_source_trans::iterator i = target.find(current.dest);
|
||||
if (i == target.end())
|
||||
{
|
||||
min_path<true> s(this, a_, target, h_);
|
||||
const state* res = s.search(current.dest->clone(), run->cycle);
|
||||
// init current to the corresponding transition.
|
||||
assert(res);
|
||||
ndfsr_trace << a_->format_state(res) << " reached" << std::endl;
|
||||
i = target.find(res);
|
||||
assert(i != target.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
ndfsr_trace << "this is a target" << std::endl;
|
||||
}
|
||||
current = i->second;
|
||||
// complete the path with the corresponding transition
|
||||
twa_run::step st = { current.source->clone(), current.label,
|
||||
current.acc };
|
||||
run->cycle.push_back(st);
|
||||
// remove this source state of target
|
||||
target.erase(i);
|
||||
}
|
||||
|
||||
if (current.dest->compare(begin) != 0)
|
||||
{
|
||||
// close the cycle by adding a path from the destination of the
|
||||
// last inserted transition to the source of the first one
|
||||
ndfsr_trace << std::endl << "looking for a path from "
|
||||
<< a_->format_state(current.dest) << " to "
|
||||
<< a_->format_state(begin) << std::endl;
|
||||
transition tmp;
|
||||
// Initialize to please GCC 4.0.1 (Darwin).
|
||||
tmp.source = tmp.dest = nullptr;
|
||||
tmp.acc = 0U;
|
||||
target.emplace(begin, tmp);
|
||||
min_path<true> s(this, a_, target, h_);
|
||||
const state* res = s.search(current.dest->clone(), run->cycle);
|
||||
assert(res);
|
||||
assert(res->compare(begin) == 0);
|
||||
(void)res;
|
||||
}
|
||||
}
|
||||
|
||||
void construct_prefix(twa_run_ptr run)
|
||||
{
|
||||
m_source_trans target;
|
||||
transition tmp;
|
||||
tmp.source = tmp.dest = nullptr; // Initialize to please GCC 4.0.
|
||||
tmp.acc = 0U;
|
||||
|
||||
// Register all states from the cycle as target of the BFS.
|
||||
for (twa_run::steps::const_iterator i = run->cycle.begin();
|
||||
i != run->cycle.end(); ++i)
|
||||
target.emplace(i->s, tmp);
|
||||
|
||||
const state* prefix_start = a_->get_init_state();
|
||||
// There are two cases: either the initial state is already on
|
||||
// the cycle, or it is not. If it is, we will have to rotate
|
||||
// the cycle so it begins on this position. Otherwise we will shift
|
||||
// the cycle so it begins on the state that follows the prefix.
|
||||
// cycle_entry_point is that state.
|
||||
const state* cycle_entry_point;
|
||||
typename m_source_trans::const_iterator ps = target.find(prefix_start);
|
||||
if (ps != target.end())
|
||||
{
|
||||
// The initial state is on the cycle.
|
||||
prefix_start->destroy();
|
||||
cycle_entry_point = ps->first->clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
// This initial state is outside the cycle. Compute the prefix.
|
||||
min_path<false> s(this, a_, target, h_);
|
||||
cycle_entry_point = s.search(prefix_start, run->prefix);
|
||||
assert(cycle_entry_point);
|
||||
cycle_entry_point = cycle_entry_point->clone();
|
||||
}
|
||||
|
||||
// Locate cycle_entry_point on the cycle.
|
||||
twa_run::steps::iterator cycle_ep_it;
|
||||
for (cycle_ep_it = run->cycle.begin();
|
||||
cycle_ep_it != run->cycle.end()
|
||||
&& cycle_entry_point->compare(cycle_ep_it->s); ++cycle_ep_it)
|
||||
continue;
|
||||
assert(cycle_ep_it != run->cycle.end());
|
||||
cycle_entry_point->destroy();
|
||||
|
||||
// Now shift the cycle so it starts on cycle_entry_point.
|
||||
run->cycle.splice(run->cycle.end(), run->cycle,
|
||||
run->cycle.begin(), cycle_ep_it);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#undef ndfsr_trace
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2011, 2012, 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 <ostream>
|
||||
#include <sstream>
|
||||
#include "neverclaim.hh"
|
||||
#include "twa/bddprint.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "reachiter.hh"
|
||||
#include "tl/print.hh"
|
||||
#include "twa/formula2bdd.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class never_claim_output
|
||||
{
|
||||
public:
|
||||
std::ostream& os_;
|
||||
bool opt_comments_ = false;
|
||||
std::vector<std::string>* sn_ = nullptr;
|
||||
bool opt_624_ = false;
|
||||
const_twa_graph_ptr aut_;
|
||||
bool fi_needed_ = false;
|
||||
bool need_accept_all_ = false;
|
||||
unsigned accept_all_ = 0;
|
||||
|
||||
public:
|
||||
never_claim_output(std::ostream& os, const char* options)
|
||||
: os_(os)
|
||||
{
|
||||
if (options)
|
||||
while (char c = *options++)
|
||||
switch (c)
|
||||
{
|
||||
case '6':
|
||||
opt_624_ = true;
|
||||
break;
|
||||
case 'c':
|
||||
opt_comments_ = true;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error
|
||||
(std::string("unknown option for print_never_claim(): ")
|
||||
+ c);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
start() const
|
||||
{
|
||||
os_ << "never {";
|
||||
auto n = aut_->get_named_prop<std::string>("automaton-name");
|
||||
if (n)
|
||||
os_ << " /* " << *n << " */";
|
||||
os_ << '\n';
|
||||
}
|
||||
|
||||
void
|
||||
end() const
|
||||
{
|
||||
if (need_accept_all_)
|
||||
{
|
||||
os_ << "accept_all:";
|
||||
print_comment(accept_all_);
|
||||
os_ << "\n skip\n";
|
||||
}
|
||||
os_ << '}' << std::endl;
|
||||
}
|
||||
|
||||
bool is_sink(unsigned n) const
|
||||
{
|
||||
auto ts = aut_->out(n);
|
||||
assert(ts.begin() != ts.end());
|
||||
auto it = ts.begin();
|
||||
return (it->cond == bddtrue) && (it->dst == n) && (++it == ts.end());
|
||||
}
|
||||
|
||||
void
|
||||
print_comment(unsigned n) const
|
||||
{
|
||||
if (sn_)
|
||||
if (n < sn_->size() && !(*sn_)[n].empty())
|
||||
os_ << " /* " << (*sn_)[n] << " */";
|
||||
}
|
||||
|
||||
void
|
||||
print_state(unsigned n) const
|
||||
{
|
||||
bool acc = aut_->state_is_accepting(n);
|
||||
if (n == aut_->get_init_state_number())
|
||||
{
|
||||
if (acc)
|
||||
os_ << "accept_init";
|
||||
else
|
||||
os_ << "T0_init";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!acc)
|
||||
os_ << "T0_S" << n;
|
||||
else if (is_sink(n))
|
||||
os_ << "accept_all";
|
||||
else
|
||||
os_ << "accept_S" << n;
|
||||
}
|
||||
}
|
||||
|
||||
void process_state(unsigned n)
|
||||
{
|
||||
if (aut_->state_is_accepting(n) && is_sink(n)
|
||||
&& n != aut_->get_init_state_number())
|
||||
{
|
||||
// We want the accept_all state at the end of the never claim.
|
||||
need_accept_all_ = true;
|
||||
accept_all_ = n;
|
||||
return;
|
||||
}
|
||||
|
||||
print_state(n);
|
||||
os_ << ':';
|
||||
print_comment(n);
|
||||
os_ << (opt_624_ ? "\n do\n" : "\n if\n");
|
||||
bool did_output = false;
|
||||
for (auto& t: aut_->out(n))
|
||||
{
|
||||
did_output = true;
|
||||
bool atom =
|
||||
opt_624_ && aut_->state_is_accepting(t.dst) && is_sink(t.dst);
|
||||
if (atom)
|
||||
os_ << " :: atomic { (";
|
||||
else
|
||||
os_ << " :: (";
|
||||
formula f = bdd_to_formula(t.cond, aut_->get_dict());
|
||||
// This is actually a Boolean formula, but the LTL printer
|
||||
// is all we have.
|
||||
print_spin_ltl(os_, f, true);
|
||||
if (atom)
|
||||
{
|
||||
os_ << ") -> assert(!(";
|
||||
print_spin_ltl(os_, f, true);
|
||||
os_ << ")) }";
|
||||
}
|
||||
else
|
||||
{
|
||||
os_ << ") -> goto ";
|
||||
print_state(t.dst);
|
||||
}
|
||||
os_ << '\n';
|
||||
}
|
||||
if (!did_output)
|
||||
{
|
||||
if (opt_624_)
|
||||
{
|
||||
os_ << " :: atomic { (false) -> assert(!(false)) }";
|
||||
}
|
||||
else
|
||||
{
|
||||
os_ << " :: (false) -> goto ";
|
||||
print_state(n);
|
||||
}
|
||||
os_ << '\n';
|
||||
}
|
||||
os_ << (opt_624_ ? " od;\n" : " fi;\n");
|
||||
}
|
||||
|
||||
void print(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
aut_ = aut;
|
||||
if (opt_comments_)
|
||||
sn_ = aut->get_named_prop<std::vector<std::string>>("state-names");
|
||||
start();
|
||||
unsigned init = aut_->get_init_state_number();
|
||||
unsigned ns = aut_->num_states();
|
||||
process_state(init);
|
||||
for (unsigned n = 0; n < ns; ++n)
|
||||
if (n != init)
|
||||
process_state(n);
|
||||
end();
|
||||
}
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
std::ostream&
|
||||
print_never_claim(std::ostream& os, const const_twa_ptr& g,
|
||||
const char* options)
|
||||
{
|
||||
if (!(g->acc().is_buchi() || g->acc().is_tt()))
|
||||
throw std::runtime_error
|
||||
("Never claim output only supports Büchi acceptance");
|
||||
never_claim_output d(os, options);
|
||||
auto aut = std::dynamic_pointer_cast<const twa_graph>(g);
|
||||
if (!aut)
|
||||
aut = make_twa_graph(g, twa::prop_set::all());
|
||||
d.print(aut);
|
||||
return os;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2011, 2012, 2013, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
#include "twa/fwd.hh"
|
||||
#include "misc/common.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_io
|
||||
/// \brief Print reachable states in Spin never claim format.
|
||||
///
|
||||
/// \param os The output stream to print on.
|
||||
/// \param g The (state-based degeneralized) automaton to output.
|
||||
/// There should be only one acceptance condition, and
|
||||
/// all the transitions of a state should be either all accepting
|
||||
/// or all unaccepting. If your automaton does not satisfies
|
||||
/// these requirements, call degeneralize() first.
|
||||
/// \param opt a string of option: 'c' to comment each state
|
||||
SPOT_API std::ostream&
|
||||
print_never_claim(std::ostream& os,
|
||||
const const_twa_ptr& g,
|
||||
const char* opt = nullptr);
|
||||
}
|
||||
|
|
@ -1,475 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
#include "postproc.hh"
|
||||
#include "minimize.hh"
|
||||
#include "simulation.hh"
|
||||
#include "sccfilter.hh"
|
||||
#include "degen.hh"
|
||||
#include "stripacc.hh"
|
||||
#include <cstdlib>
|
||||
#include "misc/optionmap.hh"
|
||||
#include "powerset.hh"
|
||||
#include "isdet.hh"
|
||||
#include "dtbasat.hh"
|
||||
#include "dtwasat.hh"
|
||||
#include "complete.hh"
|
||||
#include "totgba.hh"
|
||||
#include "sbacc.hh"
|
||||
#include "sepsets.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static twa_graph_ptr
|
||||
ensure_ba(twa_graph_ptr& a)
|
||||
{
|
||||
if (a->num_sets() == 0)
|
||||
{
|
||||
auto m = a->set_buchi();
|
||||
for (auto& t: a->edges())
|
||||
t.acc = m;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
postprocessor::postprocessor(const option_map* opt)
|
||||
: type_(TGBA), pref_(Small), level_(High),
|
||||
degen_reset_(true), degen_order_(false), degen_cache_(true),
|
||||
degen_lskip_(true), degen_lowinit_(false), simul_(-1),
|
||||
scc_filter_(-1), ba_simul_(-1),
|
||||
tba_determinisation_(false), sat_minimize_(0), sat_acc_(0),
|
||||
sat_states_(0), state_based_(false), wdba_minimize_(true)
|
||||
{
|
||||
if (opt)
|
||||
{
|
||||
degen_order_ = opt->get("degen-order", 0);
|
||||
degen_reset_ = opt->get("degen-reset", 1);
|
||||
degen_cache_ = opt->get("degen-lcache", 1);
|
||||
degen_lskip_ = opt->get("degen-lskip", 1);
|
||||
degen_lowinit_ = opt->get("degen-lowinit", 0);
|
||||
simul_ = opt->get("simul", -1);
|
||||
scc_filter_ = opt->get("scc-filter", -1);
|
||||
ba_simul_ = opt->get("ba-simul", -1);
|
||||
tba_determinisation_ = opt->get("tba-det", 0);
|
||||
sat_minimize_ = opt->get("sat-minimize", 0);
|
||||
sat_acc_ = opt->get("sat-acc", 0);
|
||||
sat_states_ = opt->get("sat-states", 0);
|
||||
state_based_ = opt->get("state-based", 0);
|
||||
wdba_minimize_ = opt->get("wdba-minimize", 1);
|
||||
|
||||
if (sat_acc_ && sat_minimize_ == 0)
|
||||
sat_minimize_ = 1; // 2?
|
||||
if (sat_states_ && sat_minimize_ == 0)
|
||||
sat_minimize_ = 1;
|
||||
if (sat_minimize_)
|
||||
{
|
||||
tba_determinisation_ = 1;
|
||||
if (sat_acc_ <= 0)
|
||||
sat_acc_ = -1;
|
||||
if (sat_states_ <= 0)
|
||||
sat_states_ = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
postprocessor::do_simul(const twa_graph_ptr& a, int opt)
|
||||
{
|
||||
if (!has_separate_sets(a))
|
||||
return a;
|
||||
switch (opt)
|
||||
{
|
||||
case 0:
|
||||
return a;
|
||||
case 1:
|
||||
return simulation(a);
|
||||
case 2:
|
||||
return cosimulation(a);
|
||||
case 3:
|
||||
default:
|
||||
return iterated_simulations(a);
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
postprocessor::do_sba_simul(const twa_graph_ptr& a, int opt)
|
||||
{
|
||||
if (ba_simul_ <= 0)
|
||||
return a;
|
||||
switch (opt)
|
||||
{
|
||||
case 0:
|
||||
return a;
|
||||
case 1:
|
||||
return simulation_sba(a);
|
||||
case 2:
|
||||
return cosimulation_sba(a);
|
||||
case 3:
|
||||
default:
|
||||
return iterated_simulations_sba(a);
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
postprocessor::do_degen(const twa_graph_ptr& a)
|
||||
{
|
||||
auto d = degeneralize(a,
|
||||
degen_reset_, degen_order_,
|
||||
degen_cache_, degen_lskip_,
|
||||
degen_lowinit_);
|
||||
return do_sba_simul(d, ba_simul_);
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
postprocessor::do_scc_filter(const twa_graph_ptr& a, bool arg)
|
||||
{
|
||||
if (scc_filter_ == 0)
|
||||
return a;
|
||||
// If the automaton is weak, using transition-based acceptance
|
||||
// won't help, so let's preserve it.
|
||||
if ((state_based_ || a->prop_inherently_weak()) && a->prop_state_acc())
|
||||
return scc_filter_states(a, arg);
|
||||
else
|
||||
return scc_filter(a, arg);
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
postprocessor::do_scc_filter(const twa_graph_ptr& a)
|
||||
{
|
||||
return do_scc_filter(a, scc_filter_ > 1);
|
||||
}
|
||||
|
||||
#define PREF_ (pref_ & (Small | Deterministic))
|
||||
#define COMP_ (pref_ & Complete)
|
||||
#define SBACC_ (pref_ & SBAcc)
|
||||
|
||||
twa_graph_ptr
|
||||
postprocessor::run(twa_graph_ptr a, formula f)
|
||||
{
|
||||
if (simul_ < 0)
|
||||
simul_ = (level_ == Low) ? 1 : 3;
|
||||
if (ba_simul_ < 0)
|
||||
ba_simul_ = (level_ == High) ? 3 : 0;
|
||||
if (scc_filter_ < 0)
|
||||
scc_filter_ = 1;
|
||||
if (type_ == BA || SBACC_)
|
||||
state_based_ = true;
|
||||
|
||||
bool tgb_used = false;
|
||||
if (type_ != Generic && !a->acc().is_generalized_buchi())
|
||||
{
|
||||
a = to_generalized_buchi(a);
|
||||
tgb_used = true;
|
||||
}
|
||||
|
||||
if (PREF_ == Any && level_ == Low)
|
||||
if (type_ == Generic
|
||||
|| type_ == TGBA
|
||||
|| (type_ == BA && a->is_sba())
|
||||
|| (type_ == Monitor && a->num_sets() == 0))
|
||||
{
|
||||
if (tgb_used)
|
||||
a = do_scc_filter(a, true);
|
||||
if (COMP_)
|
||||
a = complete(a);
|
||||
if (SBACC_)
|
||||
a = sbacc(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
int original_acc = a->num_sets();
|
||||
|
||||
// Remove useless SCCs.
|
||||
if (type_ == Monitor)
|
||||
// Do not bother about acceptance conditions, they will be
|
||||
// ignored.
|
||||
a = scc_filter_states(a);
|
||||
else
|
||||
a = do_scc_filter(a, (PREF_ == Any));
|
||||
|
||||
if (type_ == Monitor)
|
||||
{
|
||||
if (PREF_ == Deterministic)
|
||||
a = minimize_monitor(a);
|
||||
else
|
||||
strip_acceptance_here(a);
|
||||
|
||||
if (PREF_ == Any)
|
||||
return a;
|
||||
|
||||
a = do_simul(a, simul_);
|
||||
|
||||
// For Small,High we return the smallest between the output of
|
||||
// the simulation, and that of the deterministic minimization.
|
||||
if (PREF_ == Small && level_ == High && simul_)
|
||||
{
|
||||
auto m = minimize_monitor(a);
|
||||
if (m->num_states() < a->num_states())
|
||||
a = m;
|
||||
}
|
||||
if (COMP_)
|
||||
a = complete(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
if (PREF_ == Any)
|
||||
{
|
||||
if (type_ == BA)
|
||||
a = do_degen(a);
|
||||
if (COMP_)
|
||||
a = complete(a);
|
||||
if (SBACC_)
|
||||
a = sbacc(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
bool dba_is_wdba = false;
|
||||
bool dba_is_minimal = false;
|
||||
twa_graph_ptr dba = nullptr;
|
||||
twa_graph_ptr sim = nullptr;
|
||||
|
||||
// (Small,Low) is the only configuration where we do not run
|
||||
// WDBA-minimization.
|
||||
if ((PREF_ != Small || level_ != Low) && wdba_minimize_)
|
||||
{
|
||||
bool reject_bigger = (PREF_ == Small) && (level_ == Medium);
|
||||
dba = minimize_obligation(a, f, nullptr, reject_bigger);
|
||||
if (dba && dba->prop_inherently_weak() && dba->prop_deterministic())
|
||||
{
|
||||
// The WDBA is a BA, so no degeneralization is required.
|
||||
// We just need to add an acceptance set if there is none.
|
||||
dba_is_minimal = dba_is_wdba = true;
|
||||
if (type_ == BA)
|
||||
ensure_ba(dba);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Minimization failed.
|
||||
dba = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Run a simulation when wdba failed (or was not run), or
|
||||
// at hard levels if we want a small output.
|
||||
if (!dba || (level_ == High && PREF_ == Small))
|
||||
{
|
||||
if (((SBACC_ && a->prop_state_acc())
|
||||
|| (type_ == BA && a->is_sba()))
|
||||
&& !tba_determinisation_)
|
||||
{
|
||||
sim = do_sba_simul(a, ba_simul_);
|
||||
}
|
||||
else
|
||||
{
|
||||
sim = do_simul(a, simul_);
|
||||
// Degeneralize the result of the simulation if needed.
|
||||
// No need to do that if tba_determinisation_ will be used.
|
||||
if (type_ == BA && !tba_determinisation_)
|
||||
sim = do_degen(sim);
|
||||
else if (SBACC_ && !tba_determinisation_)
|
||||
sim = sbacc(sim);
|
||||
}
|
||||
}
|
||||
|
||||
// If WDBA failed, but the simulation returned a deterministic
|
||||
// automaton, use it as dba.
|
||||
assert(dba || sim);
|
||||
if (!dba && is_deterministic(sim))
|
||||
{
|
||||
std::swap(sim, dba);
|
||||
// We postponed degeneralization above i case we would need
|
||||
// to perform TBA-determinisation, but now it is clear
|
||||
// that we won't perform it. So do degeneralize.
|
||||
if (tba_determinisation_)
|
||||
{
|
||||
if (type_ == BA)
|
||||
{
|
||||
dba = do_degen(dba);
|
||||
assert(is_deterministic(dba));
|
||||
}
|
||||
else if (SBACC_)
|
||||
{
|
||||
dba = sbacc(dba);
|
||||
assert(is_deterministic(dba));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a DBA, attempt tba-determinization if requested.
|
||||
if (tba_determinisation_ && !dba)
|
||||
{
|
||||
twa_graph_ptr tmpd = nullptr;
|
||||
if (PREF_ == Deterministic
|
||||
&& f
|
||||
&& f.is_syntactic_recurrence()
|
||||
&& sim->num_sets() > 1)
|
||||
tmpd = degeneralize_tba(sim);
|
||||
|
||||
auto in = tmpd ? tmpd : sim;
|
||||
|
||||
// These thresholds are arbitrary.
|
||||
//
|
||||
// For producing Small automata, we assume that a
|
||||
// deterministic automaton that is twice the size of the
|
||||
// original will never get reduced to a smaller one. We also
|
||||
// do not want more than 2^13 cycles in an SCC.
|
||||
//
|
||||
// For Deterministic automata, we accept automata that
|
||||
// are 8 times bigger, with no more that 2^15 cycle per SCC.
|
||||
// The cycle threshold is the most important limit here. You
|
||||
// may up it if you want to try producing larger automata.
|
||||
auto tmp =
|
||||
tba_determinize_check(in,
|
||||
(PREF_ == Small) ? 2 : 8,
|
||||
1 << ((PREF_ == Small) ? 13 : 15),
|
||||
f);
|
||||
if (tmp && tmp != in)
|
||||
{
|
||||
// There is no point in running the reverse simulation on
|
||||
// a deterministic automaton, since all prefixes are
|
||||
// unique.
|
||||
dba = simulation(tmp);
|
||||
}
|
||||
if (dba && PREF_ == Deterministic)
|
||||
{
|
||||
// disregard the result of the simulation.
|
||||
sim = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// degeneralize sim, because we did not do it earlier
|
||||
if (type_ == BA)
|
||||
sim = do_degen(sim);
|
||||
}
|
||||
}
|
||||
|
||||
// Now dba contains either the result of WDBA-minimization (in
|
||||
// that case dba_is_wdba=true), or some deterministic automaton
|
||||
// that is either the result of the simulation or of the
|
||||
// TBA-determinization (dba_is_wdba=false in both cases). If the
|
||||
// dba is a WDBA, we do not have to run SAT-minimization. A
|
||||
// negative value in sat_minimize_ can for its use for debugging.
|
||||
if (sat_minimize_ && dba && (!dba_is_wdba || sat_minimize_ < 0))
|
||||
{
|
||||
unsigned target_acc;
|
||||
if (type_ == BA)
|
||||
target_acc = 1;
|
||||
else if (sat_acc_ != -1)
|
||||
target_acc = sat_acc_;
|
||||
else
|
||||
// Take the number of acceptance conditions from the input
|
||||
// automaton, not from dba, because dba often has been
|
||||
// degeneralized by tba_determinize_check(). Make sure it
|
||||
// is at least 1.
|
||||
target_acc = original_acc > 0 ? original_acc : 1;
|
||||
|
||||
const_twa_graph_ptr in = nullptr;
|
||||
if (target_acc == 1)
|
||||
{
|
||||
// If we are seeking a minimal DBA with unknown number of
|
||||
// states, then we should start from the degeneralized,
|
||||
// because the input TBA might be smaller.
|
||||
if (state_based_)
|
||||
in = degeneralize(dba);
|
||||
else if (dba->num_sets() != 1)
|
||||
in = degeneralize_tba(dba);
|
||||
else
|
||||
in = dba;
|
||||
}
|
||||
else
|
||||
{
|
||||
in = dba;
|
||||
}
|
||||
|
||||
twa_graph_ptr res = complete(in);
|
||||
if (target_acc == 1)
|
||||
{
|
||||
if (sat_states_ != -1)
|
||||
res = dtba_sat_synthetize(res, sat_states_, state_based_);
|
||||
else if (sat_minimize_ == 1 || sat_minimize_ == -1)
|
||||
res = dtba_sat_minimize(res, state_based_);
|
||||
else // sat_minimize_ == 2
|
||||
res = dtba_sat_minimize_dichotomy(res, state_based_);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sat_states_ != -1)
|
||||
res = dtwa_sat_synthetize
|
||||
(res, target_acc, acc_cond::generalized_buchi(target_acc),
|
||||
sat_states_, state_based_);
|
||||
else if (sat_minimize_ == 1 || sat_minimize_ == -1)
|
||||
res = dtwa_sat_minimize
|
||||
(res, target_acc, acc_cond::generalized_buchi(target_acc),
|
||||
state_based_);
|
||||
else // sat_minimize_ == 2
|
||||
res = dtwa_sat_minimize_dichotomy
|
||||
(res, target_acc, acc_cond::generalized_buchi(target_acc),
|
||||
state_based_);
|
||||
}
|
||||
|
||||
if (res)
|
||||
{
|
||||
dba = do_scc_filter(res, true);
|
||||
dba_is_minimal = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Degeneralize the dba resulting from tba-determinization or
|
||||
// sat-minimization (which is a TBA) if requested and needed.
|
||||
if (dba && !dba_is_wdba && type_ == BA
|
||||
&& !(dba_is_minimal && state_based_ && dba->num_sets() == 1))
|
||||
dba = degeneralize(dba);
|
||||
|
||||
if (dba && sim)
|
||||
{
|
||||
if (dba->num_states() > sim->num_states())
|
||||
dba = nullptr;
|
||||
else
|
||||
sim = nullptr;
|
||||
}
|
||||
|
||||
if (level_ == High && scc_filter_ != 0)
|
||||
{
|
||||
if (dba)
|
||||
{
|
||||
// Do that even for WDBA, to remove marks from transitions
|
||||
// leaving trivial SCCs.
|
||||
dba = do_scc_filter(dba, true);
|
||||
assert(!sim);
|
||||
}
|
||||
else if (sim)
|
||||
{
|
||||
sim = do_scc_filter(sim, true);
|
||||
assert(!dba);
|
||||
}
|
||||
}
|
||||
|
||||
sim = dba ? dba : sim;
|
||||
|
||||
if (COMP_)
|
||||
sim = complete(sim);
|
||||
if (SBACC_)
|
||||
sim = sbacc(sim);
|
||||
|
||||
return sim;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
class option_map;
|
||||
|
||||
/// \addtogroup twa_reduction
|
||||
/// @{
|
||||
|
||||
/// \brief Wrap TGBA/BA/Monitor post-processing algorithms in an
|
||||
/// easy interface.
|
||||
///
|
||||
/// This class is a shell around scc_filter(),
|
||||
/// minimize_obligation(), simulation(), iterated_simulations(), and
|
||||
/// degeneralize(). These different algorithms will be combined
|
||||
/// depending on the various options set with set_type(),
|
||||
/// set_pref(), and set_level().
|
||||
///
|
||||
/// This helps hiding some of the logic required to combine these
|
||||
/// simplifications efficiently (e.g., there is no point calling
|
||||
/// degeneralize() or any simulation when minimize_obligation()
|
||||
/// succeeded.)
|
||||
///
|
||||
/// Use set_pref() method to specify whether you favor
|
||||
/// deterministic automata or small automata. If you don't care,
|
||||
/// less post processing will be done.
|
||||
///
|
||||
/// The set_level() method lets you set the optimization level.
|
||||
/// A higher level enables more costly post-processings. For instance
|
||||
/// pref=Small,level=High will try two different post-processings
|
||||
/// (one with minimize_obligation(), and one with
|
||||
/// iterated_simulations()) an keep the smallest result.
|
||||
/// pref=Small,level=Medium will only try the iterated_simulations()
|
||||
/// when minimized_obligation failed to produce an automaton smaller
|
||||
/// than its input. pref=Small,level=Low will only run
|
||||
/// simulation().
|
||||
class SPOT_API postprocessor
|
||||
{
|
||||
public:
|
||||
/// \brief Construct a postprocessor.
|
||||
///
|
||||
/// The \a opt argument can be used to pass extra fine-tuning
|
||||
/// options used for debugging or benchmarking.
|
||||
postprocessor(const option_map* opt = nullptr);
|
||||
|
||||
enum output_type { TGBA, BA, Monitor, Generic };
|
||||
void
|
||||
set_type(output_type type)
|
||||
{
|
||||
type_ = type;
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
Any = 0,
|
||||
Small = 1, // Small and Deterministic
|
||||
Deterministic = 2, // are exclusive choices.
|
||||
Complete = 4,
|
||||
SBAcc = 8, // State-based acceptance.
|
||||
Unambiguous = 16,
|
||||
};
|
||||
typedef int output_pref;
|
||||
|
||||
void
|
||||
set_pref(output_pref pref)
|
||||
{
|
||||
pref_ = pref;
|
||||
}
|
||||
|
||||
enum optimization_level { Low, Medium, High };
|
||||
void
|
||||
set_level(optimization_level level)
|
||||
{
|
||||
level_ = level;
|
||||
}
|
||||
|
||||
/// \brief Optimize an automaton.
|
||||
///
|
||||
/// The returned automaton might be a new automaton,
|
||||
/// or an in-place modification of the \a input automaton.
|
||||
twa_graph_ptr run(twa_graph_ptr input, formula f = nullptr);
|
||||
|
||||
protected:
|
||||
twa_graph_ptr do_simul(const twa_graph_ptr& input, int opt);
|
||||
twa_graph_ptr do_sba_simul(const twa_graph_ptr& input, int opt);
|
||||
twa_graph_ptr do_degen(const twa_graph_ptr& input);
|
||||
twa_graph_ptr do_scc_filter(const twa_graph_ptr& a, bool arg);
|
||||
twa_graph_ptr do_scc_filter(const twa_graph_ptr& a);
|
||||
|
||||
output_type type_;
|
||||
int pref_;
|
||||
optimization_level level_;
|
||||
// Fine-tuning options fetched from the option_map.
|
||||
bool degen_reset_;
|
||||
bool degen_order_;
|
||||
int degen_cache_;
|
||||
bool degen_lskip_;
|
||||
bool degen_lowinit_;
|
||||
int simul_;
|
||||
int scc_filter_;
|
||||
int ba_simul_;
|
||||
bool tba_determinisation_;
|
||||
int sat_minimize_;
|
||||
int sat_acc_;
|
||||
int sat_states_;
|
||||
bool state_based_;
|
||||
bool wdba_minimize_;
|
||||
};
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,437 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2010, 2011, 2013, 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 <set>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include "powerset.hh"
|
||||
#include "misc/hash.hh"
|
||||
#include "twaalgos/powerset.hh"
|
||||
#include "twaalgos/sccinfo.hh"
|
||||
#include "twaalgos/cycles.hh"
|
||||
#include "twaalgos/gtec/gtec.hh"
|
||||
#include "twaalgos/product.hh"
|
||||
#include "twa/bddprint.hh"
|
||||
#include "twaalgos/gtec/gtec.hh"
|
||||
#include "twaalgos/sccfilter.hh"
|
||||
#include "twaalgos/ltl2tgba_fm.hh"
|
||||
#include "twaalgos/complement.hh"
|
||||
#include "twaalgos/remfin.hh"
|
||||
#include "misc/bitvect.hh"
|
||||
#include "misc/bddlt.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
static unsigned
|
||||
number_of_variables(bdd vin)
|
||||
{
|
||||
unsigned nap = 0;
|
||||
int v = vin.id();
|
||||
while (v != 1)
|
||||
{
|
||||
v = bdd_high(v);
|
||||
++nap;
|
||||
}
|
||||
return nap;
|
||||
}
|
||||
|
||||
static power_map::power_state
|
||||
bv_to_ps(const bitvect* in)
|
||||
{
|
||||
power_map::power_state ps;
|
||||
unsigned ns = in->size();
|
||||
for (unsigned pos = 0; pos < ns; ++pos)
|
||||
if (in->get(pos))
|
||||
ps.insert(pos);
|
||||
return ps;
|
||||
}
|
||||
|
||||
struct bv_hash
|
||||
{
|
||||
size_t operator()(const bitvect* bv) const
|
||||
{
|
||||
return bv->hash();
|
||||
}
|
||||
};
|
||||
|
||||
struct bv_equal
|
||||
{
|
||||
bool operator()(const bitvect* bvl, const bitvect* bvr) const
|
||||
{
|
||||
return *bvl == *bvr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
tgba_powerset(const const_twa_graph_ptr& aut, power_map& pm, bool merge)
|
||||
{
|
||||
bdd allap = bddtrue;
|
||||
{
|
||||
typedef std::set<bdd, bdd_less_than> sup_map;
|
||||
sup_map sup;
|
||||
// Record occurrences of all guards
|
||||
for (auto& t: aut->edges())
|
||||
sup.emplace(t.cond);
|
||||
for (auto& i: sup)
|
||||
allap &= bdd_support(i);
|
||||
}
|
||||
|
||||
unsigned nap = number_of_variables(allap);
|
||||
|
||||
// Call this before aut->num_states(), since it might add a state.
|
||||
unsigned init_num = aut->get_init_state_number();
|
||||
|
||||
unsigned ns = aut->num_states();
|
||||
assert(ns > 0);
|
||||
|
||||
if ((-1UL / ns) >> nap == 0)
|
||||
throw std::runtime_error("too many atomic propositions (or states)");
|
||||
|
||||
// Build a correspondence between conjunctions of APs and unsigned
|
||||
// indexes.
|
||||
std::vector<bdd> num2bdd;
|
||||
num2bdd.reserve(1UL << nap);
|
||||
std::map<bdd, unsigned, bdd_less_than> bdd2num;
|
||||
bdd all = bddtrue;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd one = bdd_satoneset(all, allap, bddfalse);
|
||||
all -= one;
|
||||
bdd2num.emplace(one, num2bdd.size());
|
||||
num2bdd.push_back(one);
|
||||
}
|
||||
|
||||
size_t nc = num2bdd.size(); // number of conditions
|
||||
assert(nc == (1UL << nap));
|
||||
|
||||
// An array of bit vectors of size 'ns'. Each original state is
|
||||
// represented by 'nc' bitvectors representing the possible
|
||||
// destinations for each condition.
|
||||
auto bv = std::unique_ptr<bitvect_array>(make_bitvect_array(ns, ns * nc));
|
||||
|
||||
for (unsigned src = 0; src < ns; ++src)
|
||||
{
|
||||
size_t base = src * nc;
|
||||
for (auto& t: aut->out(src))
|
||||
{
|
||||
bdd all = t.cond;
|
||||
while (all != bddfalse)
|
||||
{
|
||||
bdd one = bdd_satoneset(all, allap, bddfalse);
|
||||
all -= one;
|
||||
unsigned num = bdd2num[one];
|
||||
bv->at(base + num).set(t.dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef power_map::power_state power_state;
|
||||
|
||||
typedef std::unordered_map<bitvect*, int, bv_hash, bv_equal> power_set;
|
||||
power_set seen;
|
||||
|
||||
std::vector<const bitvect*>toclean;
|
||||
|
||||
auto res = make_twa_graph(aut->get_dict());
|
||||
res->copy_ap_of(aut);
|
||||
|
||||
{
|
||||
auto bvi = make_bitvect(ns);
|
||||
bvi->set(init_num);
|
||||
power_state ps{init_num};
|
||||
unsigned num = res->new_state();
|
||||
res->set_init_state(num);
|
||||
seen[bvi] = num;
|
||||
assert(pm.map_.size() == num);
|
||||
pm.map_.emplace_back(std::move(ps));
|
||||
toclean.push_back(bvi);
|
||||
}
|
||||
|
||||
// outgoing map
|
||||
auto om = std::unique_ptr<bitvect_array>(make_bitvect_array(ns, nc));
|
||||
|
||||
for (unsigned src_num = 0; src_num < res->num_states(); ++src_num)
|
||||
{
|
||||
om->clear_all();
|
||||
|
||||
const power_state& src = pm.states_of(src_num);
|
||||
|
||||
for (auto s: src)
|
||||
{
|
||||
size_t base = s * nc;
|
||||
for (unsigned c = 0; c < nc; ++c)
|
||||
om->at(c) |= bv->at(base + c);
|
||||
}
|
||||
for (unsigned c = 0; c < nc; ++c)
|
||||
{
|
||||
auto dst = &om->at(c);
|
||||
if (dst->is_fully_clear())
|
||||
continue;
|
||||
auto i = seen.find(dst);
|
||||
unsigned dst_num;
|
||||
if (i != seen.end())
|
||||
{
|
||||
dst_num = i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst_num = res->new_state();
|
||||
auto dst2 = dst->clone();
|
||||
seen[dst2] = dst_num;
|
||||
toclean.push_back(dst2);
|
||||
auto ps = bv_to_ps(dst);
|
||||
assert(pm.map_.size() == dst_num);
|
||||
pm.map_.emplace_back(std::move(ps));
|
||||
}
|
||||
res->new_edge(src_num, dst_num, num2bdd[c]);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto v: toclean)
|
||||
delete v;
|
||||
if (merge)
|
||||
res->merge_edges();
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
tgba_powerset(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
power_map pm;
|
||||
return tgba_powerset(aut, pm);
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class fix_scc_acceptance final: protected enumerate_cycles
|
||||
{
|
||||
public:
|
||||
typedef dfs_stack::const_iterator cycle_iter;
|
||||
typedef twa_graph_edge_data trans;
|
||||
typedef std::set<trans*> edge_set;
|
||||
typedef std::vector<edge_set> set_set;
|
||||
protected:
|
||||
const_twa_graph_ptr ref_;
|
||||
power_map& refmap_;
|
||||
edge_set reject_; // set of rejecting edges
|
||||
set_set accept_; // set of cycles that are accepting
|
||||
edge_set all_; // all non rejecting edges
|
||||
unsigned threshold_; // maximum count of enumerated cycles
|
||||
unsigned cycles_left_; // count of cycles left to explore
|
||||
|
||||
public:
|
||||
fix_scc_acceptance(const scc_info& sm, const_twa_graph_ptr ref,
|
||||
power_map& refmap, unsigned threshold)
|
||||
: enumerate_cycles(sm), ref_(ref), refmap_(refmap),
|
||||
threshold_(threshold)
|
||||
{
|
||||
}
|
||||
|
||||
bool fix_scc(const int m)
|
||||
{
|
||||
reject_.clear();
|
||||
accept_.clear();
|
||||
cycles_left_ = threshold_;
|
||||
run(m);
|
||||
|
||||
// std::cerr << "SCC #" << m << '\n';
|
||||
// std::cerr << "REJECT: ";
|
||||
// print_set(std::cerr, reject_) << '\n';
|
||||
// std::cerr << "ALL: ";
|
||||
// print_set(std::cerr, all_) << '\n';
|
||||
// for (set_set::const_iterator j = accept_.begin();
|
||||
// j != accept_.end(); ++j)
|
||||
// {
|
||||
// std::cerr << "ACCEPT: ";
|
||||
// print_set(std::cerr, *j) << '\n';
|
||||
// }
|
||||
|
||||
auto acc = aut_->acc().all_sets();
|
||||
for (auto i: all_)
|
||||
i->acc = acc;
|
||||
return threshold_ != 0 && cycles_left_ == 0;
|
||||
}
|
||||
|
||||
bool is_cycle_accepting(cycle_iter begin, edge_set& ts) const
|
||||
{
|
||||
auto a = std::const_pointer_cast<twa_graph>(aut_);
|
||||
|
||||
// Build an automaton representing this loop.
|
||||
auto loop_a = make_twa_graph(aut_->get_dict());
|
||||
int loop_size = std::distance(begin, dfs_.end());
|
||||
loop_a->new_states(loop_size);
|
||||
int n;
|
||||
cycle_iter i;
|
||||
for (n = 1, i = begin; n <= loop_size; ++n, ++i)
|
||||
{
|
||||
trans* t = &a->edge_data(i->succ);
|
||||
loop_a->new_edge(n - 1, n % loop_size, t->cond);
|
||||
if (reject_.find(t) == reject_.end())
|
||||
ts.insert(t);
|
||||
}
|
||||
assert(i == dfs_.end());
|
||||
|
||||
unsigned loop_a_init = loop_a->get_init_state_number();
|
||||
assert(loop_a_init == 0);
|
||||
|
||||
// Check if the loop is accepting in the original automaton.
|
||||
bool accepting = false;
|
||||
|
||||
// Iterate on each original state corresponding to the
|
||||
// start of the loop in the determinized automaton.
|
||||
for (auto s: refmap_.states_of(begin->s))
|
||||
{
|
||||
// Check the product between LOOP_A, and ORIG_A starting
|
||||
// in S.
|
||||
if (!product(loop_a, ref_, loop_a_init, s)->is_empty())
|
||||
{
|
||||
accepting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return accepting;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
print_set(std::ostream& o, const edge_set& s) const
|
||||
{
|
||||
o << "{ ";
|
||||
for (auto i: s)
|
||||
o << i << ' ';
|
||||
o << '}';
|
||||
return o;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
cycle_found(unsigned start) override
|
||||
{
|
||||
cycle_iter i = dfs_.begin();
|
||||
while (i->s != start)
|
||||
++i;
|
||||
edge_set ts;
|
||||
bool is_acc = is_cycle_accepting(i, ts);
|
||||
do
|
||||
++i;
|
||||
while (i != dfs_.end());
|
||||
if (is_acc)
|
||||
{
|
||||
accept_.push_back(ts);
|
||||
all_.insert(ts.begin(), ts.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto t: ts)
|
||||
{
|
||||
reject_.insert(t);
|
||||
for (auto& j: accept_)
|
||||
j.erase(t);
|
||||
all_.erase(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Abort this algorithm if we have seen too much cycles, i.e.,
|
||||
// when cycle_left_ *reaches* 0. (If cycle_left_ == 0, that
|
||||
// means we had no limit.)
|
||||
return (cycles_left_ == 0) || --cycles_left_;
|
||||
}
|
||||
};
|
||||
|
||||
static bool
|
||||
fix_dba_acceptance(twa_graph_ptr det,
|
||||
const_twa_graph_ptr ref, power_map& refmap,
|
||||
unsigned threshold)
|
||||
{
|
||||
det->copy_acceptance_of(ref);
|
||||
|
||||
scc_info sm(det);
|
||||
|
||||
unsigned scc_count = sm.scc_count();
|
||||
|
||||
fix_scc_acceptance fsa(sm, ref, refmap, threshold);
|
||||
|
||||
for (unsigned m = 0; m < scc_count; ++m)
|
||||
if (!sm.is_trivial(m))
|
||||
if (fsa.fix_scc(m))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
tba_determinize(const const_twa_graph_ptr& aut,
|
||||
unsigned threshold_states, unsigned threshold_cycles)
|
||||
{
|
||||
power_map pm;
|
||||
// Do not merge edges in the deterministic automaton. If we
|
||||
// add two self-loops labeled by "a" and "!a", we do not want
|
||||
// these to be merged as "1" before the acceptance has been fixed.
|
||||
auto det = tgba_powerset(aut, pm, false);
|
||||
|
||||
if ((threshold_states > 0)
|
||||
&& (pm.map_.size() > aut->num_states() * threshold_states))
|
||||
return nullptr;
|
||||
if (fix_dba_acceptance(det, aut, pm, threshold_cycles))
|
||||
return nullptr;
|
||||
det->merge_edges();
|
||||
return det;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
tba_determinize_check(const twa_graph_ptr& aut,
|
||||
unsigned threshold_states,
|
||||
unsigned threshold_cycles,
|
||||
formula f,
|
||||
const_twa_graph_ptr neg_aut)
|
||||
{
|
||||
if (f == nullptr && neg_aut == nullptr)
|
||||
return nullptr;
|
||||
if (aut->num_sets() > 1)
|
||||
return nullptr;
|
||||
|
||||
auto det = tba_determinize(aut, threshold_states, threshold_cycles);
|
||||
|
||||
if (!det)
|
||||
return nullptr;
|
||||
|
||||
if (neg_aut == nullptr)
|
||||
{
|
||||
neg_aut = ltl_to_tgba_fm(formula::Not(f), aut->get_dict());
|
||||
// Remove useless SCCs.
|
||||
neg_aut = scc_filter(neg_aut, true);
|
||||
}
|
||||
|
||||
if (product(det, neg_aut)->is_empty())
|
||||
// Complement the DBA.
|
||||
if (product(aut, remove_fin(dtwa_complement(det)))->is_empty())
|
||||
// Finally, we are now sure that it was safe
|
||||
// to determinize the automaton.
|
||||
return det;
|
||||
|
||||
return aut;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
struct SPOT_API power_map
|
||||
{
|
||||
typedef std::set<unsigned> power_state;
|
||||
std::vector<power_state> map_;
|
||||
|
||||
const power_state&
|
||||
states_of(unsigned s) const
|
||||
{
|
||||
return map_.at(s);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// \ingroup twa_misc
|
||||
/// \brief Build a deterministic automaton, ignoring acceptance conditions.
|
||||
///
|
||||
/// This create a deterministic automaton that recognizes the
|
||||
/// same language as \a aut would if its acceptance conditions
|
||||
/// were ignored. This is the classical powerset algorithm.
|
||||
///
|
||||
/// If \a pm is supplied it will be filled with the set of original states
|
||||
/// associated to each state of the deterministic automaton.
|
||||
/// The \a merge argument can be set to false to prevent merging of
|
||||
/// transitions.
|
||||
//@{
|
||||
SPOT_API twa_graph_ptr
|
||||
tgba_powerset(const const_twa_graph_ptr& aut,
|
||||
power_map& pm, bool merge = true);
|
||||
SPOT_API twa_graph_ptr
|
||||
tgba_powerset(const const_twa_graph_ptr& aut);
|
||||
//@}
|
||||
|
||||
|
||||
/// \brief Determinize a TBA using the powerset construction.
|
||||
///
|
||||
/// The input automaton should have at most one acceptance
|
||||
/// condition. Beware that not all Büchi automata can be
|
||||
/// determinized, and this procedure does not ensure that the
|
||||
/// produced automaton is equivalent to \a aut.
|
||||
///
|
||||
/// The construction is adapted from Section 3.2 of:
|
||||
/// \verbatim
|
||||
/// @InProceedings{ dax.07.atva,
|
||||
/// author = {Christian Dax and Jochen Eisinger and Felix Klaedtke},
|
||||
/// title = {Mechanizing the Powerset Construction for Restricted
|
||||
/// Classes of {$\omega$}-Automata},
|
||||
/// year = 2007,
|
||||
/// series = {Lecture Notes in Computer Science},
|
||||
/// publisher = {Springer-Verlag},
|
||||
/// volume = 4762,
|
||||
/// booktitle = {Proceedings of the 5th International Symposium on
|
||||
/// Automated Technology for Verification and Analysis
|
||||
/// (ATVA'07)},
|
||||
/// editor = {Kedar S. Namjoshi and Tomohiro Yoneda and Teruo Higashino
|
||||
/// and Yoshio Okamura},
|
||||
/// month = oct
|
||||
/// }
|
||||
/// \endverbatim
|
||||
/// only adapted to work on TBA rather than BA.
|
||||
///
|
||||
/// If \a threshold_states is non null, abort the construction
|
||||
/// whenever it would build an automaton that is more than \a
|
||||
/// threshold_states time bigger (in term of states) than the
|
||||
/// original automaton.
|
||||
///
|
||||
/// If \a threshold_cycles is non null, abort the construction
|
||||
/// whenever an SCC of the constructed automaton has more than \a
|
||||
/// threshold_cycles cycles.
|
||||
SPOT_API twa_graph_ptr
|
||||
tba_determinize(const const_twa_graph_ptr& aut,
|
||||
unsigned threshold_states = 0,
|
||||
unsigned threshold_cycles = 0);
|
||||
|
||||
/// \brief Determinize a TBA and make sure it is correct.
|
||||
///
|
||||
/// Apply tba_determinize(), then check that the result is
|
||||
/// equivalent. If it isn't, return the original automaton.
|
||||
///
|
||||
/// Only one of \a f or \a neg_aut needs to be supplied. If
|
||||
/// \a neg_aut is not given, it will be built from \a f.
|
||||
///
|
||||
/// \param aut the automaton to minimize
|
||||
///
|
||||
/// \param threshold_states if non null, abort the construction
|
||||
/// whenever it would build an automaton that is more than \a
|
||||
/// threshold time bigger (in term of states) than the original
|
||||
/// automaton.
|
||||
///
|
||||
/// \param threshold_cycles can be used to abort the construction
|
||||
/// if the number of cycles in a SCC of the constructed automaton
|
||||
/// is bigger than the supplied value.
|
||||
///
|
||||
/// \param f the formula represented by the original automaton
|
||||
///
|
||||
/// \param neg_aut an automaton representing the negation of \a aut
|
||||
///
|
||||
/// \return a new tgba if the automaton could be determinized, \a aut if
|
||||
/// the automaton cannot be determinized, 0 if we do not know if the
|
||||
/// determinization is correct because neither \a f nor \a neg_aut
|
||||
/// were supplied.
|
||||
SPOT_API twa_graph_ptr
|
||||
tba_determinize_check(const twa_graph_ptr& aut,
|
||||
unsigned threshold_states = 0,
|
||||
unsigned threshold_cycles = 0,
|
||||
formula f = nullptr,
|
||||
const_twa_graph_ptr neg_aut = nullptr);
|
||||
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "product.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "twaalgos/complete.hh"
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#include "misc/hash.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
typedef std::pair<unsigned, unsigned> product_state;
|
||||
|
||||
struct product_state_hash
|
||||
{
|
||||
size_t
|
||||
operator()(product_state s) const
|
||||
{
|
||||
return wang32_hash(s.first ^ wang32_hash(s.second));
|
||||
}
|
||||
};
|
||||
|
||||
static
|
||||
twa_graph_ptr product_aux(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right,
|
||||
unsigned left_state,
|
||||
unsigned right_state,
|
||||
bool and_acc)
|
||||
{
|
||||
std::unordered_map<product_state, unsigned, product_state_hash> s2n;
|
||||
std::deque<std::pair<product_state, unsigned>> todo;
|
||||
|
||||
assert(left->get_dict() == right->get_dict());
|
||||
auto res = make_twa_graph(left->get_dict());
|
||||
res->copy_ap_of(left);
|
||||
res->copy_ap_of(right);
|
||||
auto left_num = left->num_sets();
|
||||
auto right_acc = right->get_acceptance();
|
||||
right_acc.shift_left(left_num);
|
||||
if (and_acc)
|
||||
right_acc.append_and(left->get_acceptance());
|
||||
else
|
||||
right_acc.append_or(acc_cond::acc_code(left->get_acceptance()));
|
||||
res->set_acceptance(left_num + right->num_sets(), right_acc);
|
||||
|
||||
auto v = new product_states;
|
||||
res->set_named_prop("product-states", v);
|
||||
|
||||
auto new_state =
|
||||
[&](unsigned left_state, unsigned right_state) -> unsigned
|
||||
{
|
||||
product_state x(left_state, right_state);
|
||||
auto p = s2n.emplace(x, 0);
|
||||
if (p.second) // This is a new state
|
||||
{
|
||||
p.first->second = res->new_state();
|
||||
todo.emplace_back(x, p.first->second);
|
||||
assert(p.first->second == v->size());
|
||||
v->push_back(x);
|
||||
}
|
||||
return p.first->second;
|
||||
};
|
||||
|
||||
res->set_init_state(new_state(left_state, right_state));
|
||||
if (right_acc.is_ff())
|
||||
// Do not bother doing any work if the resulting acceptance is
|
||||
// false.
|
||||
return res;
|
||||
while (!todo.empty())
|
||||
{
|
||||
auto top = todo.front();
|
||||
todo.pop_front();
|
||||
for (auto& l: left->out(top.first.first))
|
||||
for (auto& r: right->out(top.first.second))
|
||||
{
|
||||
auto cond = l.cond & r.cond;
|
||||
if (cond == bddfalse)
|
||||
continue;
|
||||
auto dst = new_state(l.dst, r.dst);
|
||||
res->new_edge(top.second, dst, cond,
|
||||
res->acc().join(left->acc(), l.acc,
|
||||
right->acc(), r.acc));
|
||||
// If right is deterministic, we can abort immediately!
|
||||
}
|
||||
}
|
||||
|
||||
res->prop_deterministic(left->prop_deterministic()
|
||||
&& right->prop_deterministic());
|
||||
res->prop_stutter_invariant(left->prop_stutter_invariant()
|
||||
&& right->prop_stutter_invariant());
|
||||
res->prop_stutter_sensitive(left->prop_stutter_sensitive()
|
||||
&& right->prop_stutter_sensitive());
|
||||
res->prop_state_acc(left->prop_state_acc()
|
||||
&& right->prop_state_acc());
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr product(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right,
|
||||
unsigned left_state,
|
||||
unsigned right_state)
|
||||
{
|
||||
return product_aux(left, right, left_state, right_state, true);
|
||||
}
|
||||
|
||||
twa_graph_ptr product(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right)
|
||||
{
|
||||
return product(left, right,
|
||||
left->get_init_state_number(),
|
||||
right->get_init_state_number());
|
||||
}
|
||||
|
||||
twa_graph_ptr product_or(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right,
|
||||
unsigned left_state,
|
||||
unsigned right_state)
|
||||
{
|
||||
return product_aux(complete(left),
|
||||
complete(right),
|
||||
left_state, right_state, false);
|
||||
}
|
||||
|
||||
twa_graph_ptr product_or(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right)
|
||||
{
|
||||
return product_or(left, right,
|
||||
left->get_init_state_number(),
|
||||
right->get_init_state_number());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "misc/common.hh"
|
||||
#include "twa/fwd.hh"
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
// The tgba constructed by product() below contain property named
|
||||
// "product-states" with type product_states.
|
||||
typedef std::vector<std::pair<unsigned, unsigned>> product_states;
|
||||
|
||||
SPOT_API
|
||||
twa_graph_ptr product(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right);
|
||||
|
||||
SPOT_API
|
||||
twa_graph_ptr product(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right,
|
||||
unsigned left_state,
|
||||
unsigned right_state);
|
||||
|
||||
SPOT_API
|
||||
twa_graph_ptr product_or(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right);
|
||||
|
||||
SPOT_API
|
||||
twa_graph_ptr product_or(const const_twa_graph_ptr& left,
|
||||
const const_twa_graph_ptr& right,
|
||||
unsigned left_state,
|
||||
unsigned right_state);
|
||||
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "projrun.hh"
|
||||
#include "twa/twa.hh"
|
||||
#include "twaalgos/emptiness.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
twa_run_ptr
|
||||
project_twa_run(const const_twa_ptr& a_run,
|
||||
const const_twa_ptr& a_proj,
|
||||
const const_twa_run_ptr& run)
|
||||
{
|
||||
auto res = std::make_shared<twa_run>(a_proj);
|
||||
for (auto& i: run->prefix)
|
||||
res->prefix.emplace_back(a_run->project_state(i.s, a_proj),
|
||||
i.label, i.acc);
|
||||
for (auto& i: run->cycle)
|
||||
res->prefix.emplace_back(a_run->project_state(i.s, a_proj),
|
||||
i.label, i.acc);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014 Laboratoire de Recherche et Developpement
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "misc/common.hh"
|
||||
#include <iosfwd>
|
||||
#include "twa/fwd.hh"
|
||||
#include "twaalgos/emptiness.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
struct twa_run;
|
||||
|
||||
/// \ingroup twa_run
|
||||
/// \brief Project a twa_run on a tgba.
|
||||
///
|
||||
/// If a twa_run has been generated on a product, or any other
|
||||
/// on-the-fly algorithm with tgba operands,
|
||||
///
|
||||
/// \param run the run to replay
|
||||
/// \param a_run the automata on which the run was generated
|
||||
/// \param a_proj the automata on which to project the run
|
||||
/// \return true iff the run could be completed
|
||||
SPOT_API twa_run_ptr
|
||||
project_twa_run(const const_twa_ptr& a_run,
|
||||
const const_twa_ptr& a_proj,
|
||||
const const_twa_run_ptr& run);
|
||||
}
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2009, 2010, 2012, 2013, 2014, 2015 Laboratoire de
|
||||
// Recherche et Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005, 2007 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 "randomgraph.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
#include "misc/random.hh"
|
||||
#include "misc/bddlt.hh"
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
static unsigned
|
||||
random_deterministic_labels_rec(std::vector<bdd>& labels, int *props,
|
||||
int props_n, bdd current, unsigned n)
|
||||
{
|
||||
if (n > 1 && props_n >= 1)
|
||||
{
|
||||
bdd ap = bdd_ithvar(*props);
|
||||
++props;
|
||||
--props_n;
|
||||
|
||||
// There are m labels generated from "current & ap"
|
||||
// and n - m labels generated from "current & !ap"
|
||||
unsigned m = rrand(1, n - 1);
|
||||
if (2 * m < n)
|
||||
{
|
||||
m = n - m;
|
||||
ap = !ap;
|
||||
}
|
||||
|
||||
unsigned res = random_deterministic_labels_rec(labels, props,
|
||||
props_n,
|
||||
current & ap, m);
|
||||
res += random_deterministic_labels_rec(labels, props, props_n,
|
||||
current & !ap, n - res);
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
labels.push_back(current);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<bdd>
|
||||
random_deterministic_labels(int *props, int props_n, unsigned n)
|
||||
{
|
||||
std::vector<bdd> bddvec;
|
||||
random_deterministic_labels_rec(bddvec, props, props_n, bddtrue, n);
|
||||
return bddvec;
|
||||
}
|
||||
|
||||
static acc_cond::mark_t
|
||||
random_acc(unsigned n_accs, float a)
|
||||
{
|
||||
acc_cond::mark_t m = 0U;
|
||||
for (unsigned i = 0U; i < n_accs; ++i)
|
||||
if (drand() < a)
|
||||
m.set(i);
|
||||
return m;
|
||||
}
|
||||
|
||||
static acc_cond::mark_t
|
||||
random_acc1(unsigned n_accs)
|
||||
{
|
||||
auto u = static_cast<unsigned>(mrand(static_cast<int>(n_accs)));
|
||||
return acc_cond::mark_t({u});
|
||||
}
|
||||
|
||||
bdd
|
||||
random_labels(int* props, int props_n, float t)
|
||||
{
|
||||
int val = 0;
|
||||
int size = 0;
|
||||
bdd p = bddtrue;
|
||||
while (props_n)
|
||||
{
|
||||
if (size == 8 * sizeof(int))
|
||||
{
|
||||
p &= bdd_ibuildcube(val, size, props);
|
||||
props += size;
|
||||
val = 0;
|
||||
size = 0;
|
||||
}
|
||||
val <<= 1;
|
||||
val |= (drand() < t);
|
||||
++size;
|
||||
--props_n;
|
||||
}
|
||||
if (size > 0)
|
||||
p &= bdd_ibuildcube(val, size, props);
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
random_graph(int n, float d,
|
||||
const atomic_prop_set* ap, const bdd_dict_ptr& dict,
|
||||
unsigned n_accs, float a, float t,
|
||||
bool deterministic, bool state_acc, bool colored)
|
||||
{
|
||||
if (n <= 0)
|
||||
throw std::invalid_argument("random_graph() requires n>0 states");
|
||||
auto res = make_twa_graph(dict);
|
||||
if (deterministic)
|
||||
res->prop_deterministic(true);
|
||||
if (state_acc)
|
||||
res->prop_state_acc(true);
|
||||
|
||||
int props_n = ap->size();
|
||||
int* props = new int[props_n];
|
||||
|
||||
int pi = 0;
|
||||
for (auto i: *ap)
|
||||
props[pi++] = res->register_ap(i);
|
||||
|
||||
res->set_generalized_buchi(n_accs);
|
||||
|
||||
// Using std::unordered_set instead of std::set for these sets is 3
|
||||
// times slower (tested on a 50000 nodes example).
|
||||
typedef std::set<int> node_set;
|
||||
node_set nodes_to_process;
|
||||
node_set unreachable_nodes;
|
||||
|
||||
res->new_states(n);
|
||||
|
||||
std::vector<unsigned> state_randomizer(n);
|
||||
state_randomizer[0] = 0;
|
||||
nodes_to_process.insert(0);
|
||||
|
||||
for (int i = 1; i < n; ++i)
|
||||
{
|
||||
state_randomizer[i] = i;
|
||||
unreachable_nodes.insert(i);
|
||||
}
|
||||
|
||||
// We want to connect each node to a number of successors between
|
||||
// 1 and n. If the probability to connect to each successor is d,
|
||||
// the number of connected successors follows a binomial distribution.
|
||||
barand<nrand> bin(n - 1, d);
|
||||
|
||||
while (!nodes_to_process.empty())
|
||||
{
|
||||
auto src = *nodes_to_process.begin();
|
||||
nodes_to_process.erase(nodes_to_process.begin());
|
||||
|
||||
// Choose a random number of successors (at least one), using
|
||||
// a binomial distribution.
|
||||
unsigned nsucc = 1 + bin.rand();
|
||||
|
||||
bool saw_unreachable = false;
|
||||
|
||||
// Create NSUCC random labels.
|
||||
std::vector<bdd> labels;
|
||||
if (deterministic)
|
||||
{
|
||||
labels = random_deterministic_labels(props, props_n, nsucc);
|
||||
|
||||
// if nsucc > 2^props_n, we cannot produce nsucc deterministic
|
||||
// edges so we set it to labels.size()
|
||||
nsucc = labels.size();
|
||||
}
|
||||
else
|
||||
for (unsigned i = 0; i < nsucc; ++i)
|
||||
labels.push_back(random_labels(props, props_n, t));
|
||||
|
||||
int possibilities = n;
|
||||
unsigned dst;
|
||||
acc_cond::mark_t m = 0U;
|
||||
if (state_acc)
|
||||
m = colored ? random_acc1(n_accs) : random_acc(n_accs, a);
|
||||
|
||||
for (auto& l: labels)
|
||||
{
|
||||
if (!state_acc)
|
||||
m = colored ? random_acc1(n_accs) : random_acc(n_accs, a);
|
||||
|
||||
// No connection to unreachable successors so far. This
|
||||
// is our last chance, so force it now.
|
||||
if (--nsucc == 0
|
||||
&& !unreachable_nodes.empty()
|
||||
&& !saw_unreachable)
|
||||
{
|
||||
// Pick a random unreachable node.
|
||||
int index = mrand(unreachable_nodes.size());
|
||||
node_set::const_iterator i = unreachable_nodes.begin();
|
||||
std::advance(i, index);
|
||||
|
||||
// Link it from src.
|
||||
res->new_edge(src, *i, l, m);
|
||||
nodes_to_process.insert(*i);
|
||||
unreachable_nodes.erase(*i);
|
||||
break;
|
||||
}
|
||||
|
||||
// Pick the index of a random node.
|
||||
int index = mrand(possibilities--);
|
||||
|
||||
// Permute it with state_randomizer[possibilities], so
|
||||
// we cannot pick it again.
|
||||
dst = state_randomizer[index];
|
||||
state_randomizer[index] = state_randomizer[possibilities];
|
||||
state_randomizer[possibilities] = dst;
|
||||
|
||||
res->new_edge(src, dst, l, m);
|
||||
auto j = unreachable_nodes.find(dst);
|
||||
if (j != unreachable_nodes.end())
|
||||
{
|
||||
nodes_to_process.insert(dst);
|
||||
unreachable_nodes.erase(j);
|
||||
saw_unreachable = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The node must have at least one successor.
|
||||
assert(res->get_graph().state_storage(src).succ);
|
||||
}
|
||||
// All nodes must be reachable.
|
||||
assert(unreachable_nodes.empty());
|
||||
delete[] props;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 "tl/apcollect.hh"
|
||||
#include "tl/defaultenv.hh"
|
||||
#include "twa/bdddict.hh"
|
||||
#include "twa/acc.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_misc
|
||||
/// \brief Construct a twa randomly.
|
||||
///
|
||||
/// \param n The number of states wanted in the automata (>0). All states
|
||||
/// will be connected, and there will be no dead state.
|
||||
/// \param d The density of the automata. This is the probability
|
||||
/// (between 0.0 and 1.0), to add a transition between two
|
||||
/// states. All states have at least one outgoing transition,
|
||||
/// so \a d is considered only when adding the remaining transition.
|
||||
/// A density of 1 means all states will be connected to each other.
|
||||
/// \param ap The list of atomic property that should label the transition.
|
||||
/// \param dict The bdd_dict to used for this automata.
|
||||
/// \param n_accs The number of acceptance sets to use.
|
||||
/// If this number is non null, then there is no guarantee
|
||||
/// that the generated graph contains an accepting cycle (raise
|
||||
/// the value of \a a to improve the chances).
|
||||
/// \param a The probability (between 0.0 and 1.0) that a transition belongs
|
||||
/// to an acceptance set.
|
||||
/// \param t The probability (between 0.0 and 1.0) that an atomic proposition
|
||||
/// is true.
|
||||
/// \param deterministic build a complete and deterministic automaton
|
||||
/// \param state_acc build an automaton with state-based acceptance
|
||||
/// \param colored build an automaton in which each transition (or state)
|
||||
/// belongs to a single acceptance set.
|
||||
///
|
||||
/// This algorithms is adapted from the one in Fig 6.2 page 48 of
|
||||
/** \verbatim
|
||||
@TechReport{ tauriainen.00.a66,
|
||||
author = {Heikki Tauriainen},
|
||||
title = {Automated Testing of {B\"u}chi Automata Translators for
|
||||
{L}inear {T}emporal {L}ogic},
|
||||
address = {Espoo, Finland},
|
||||
institution = {Helsinki University of Technology, Laboratory for
|
||||
Theoretical Computer Science},
|
||||
number = {A66},
|
||||
year = {2000},
|
||||
url = {http://citeseer.nj.nec.com/tauriainen00automated.html},
|
||||
type = {Research Report},
|
||||
note = {Reprint of Master's thesis}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// Although the intent is similar, there are some differences
|
||||
/// between the above published algorithm and this implementation.
|
||||
/// First labels are on transitions, and acceptance conditions are
|
||||
/// generated too. Second, the number of successors of a node is
|
||||
/// chosen in \f$[1,n]\f$ following a normal distribution with mean
|
||||
/// \f$1+(n-1)d\f$ and variance \f$(n-1)d(1-d)\f$. (This is less
|
||||
/// accurate, but faster than considering all possible \a n
|
||||
/// successors one by one.)
|
||||
///
|
||||
/// Note that while this constructs an automaton with random
|
||||
/// acceptance sets, this does not set the acceptance condition.
|
||||
SPOT_API twa_graph_ptr
|
||||
random_graph(int n, float d,
|
||||
const atomic_prop_set* ap, const bdd_dict_ptr& dict,
|
||||
unsigned n_accs = 0, float a = 0.1, float t = 0.5,
|
||||
bool deterministic = false, bool state_acc = false,
|
||||
bool colored = false);
|
||||
|
||||
/// Build a random acceptance where each acceptance sets is used once.
|
||||
SPOT_API acc_cond::acc_code random_acceptance(unsigned n_accs);
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 <algorithm>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include "randomize.hh"
|
||||
#include "misc/random.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
void
|
||||
randomize(twa_graph_ptr& aut, bool randomize_states,
|
||||
bool randomize_edges)
|
||||
{
|
||||
if (!randomize_states && !randomize_edges)
|
||||
return;
|
||||
auto& g = aut->get_graph();
|
||||
if (randomize_states)
|
||||
{
|
||||
unsigned n = g.num_states();
|
||||
std::vector<unsigned> nums(n);
|
||||
std::iota(nums.begin(), nums.end(), 0);
|
||||
mrandom_shuffle(nums.begin(), nums.end());
|
||||
g.rename_states_(nums);
|
||||
aut->set_init_state(nums[aut->get_init_state_number()]);
|
||||
|
||||
if (auto sn =
|
||||
aut->get_named_prop<std::vector<std::string>>("state-names"))
|
||||
{
|
||||
unsigned sns = sn->size(); // Might be != n.
|
||||
auto nn = new std::vector<std::string>(n);
|
||||
for (unsigned i = 0; i < sns && i < n; ++i)
|
||||
(*nn)[nums[i]] = (*sn)[i];
|
||||
aut->set_named_prop("state-names", nn);
|
||||
}
|
||||
}
|
||||
if (randomize_edges)
|
||||
{
|
||||
g.remove_dead_edges_();
|
||||
auto& v = g.edge_vector();
|
||||
mrandom_shuffle(v.begin() + 1, v.end());
|
||||
}
|
||||
|
||||
typedef twa_graph::graph_t::edge_storage_t tr_t;
|
||||
g.sort_edges_([](const tr_t& lhs, const tr_t& rhs)
|
||||
{ return lhs.src < rhs.src; });
|
||||
g.chain_edges_();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Randomize a TGBA
|
||||
///
|
||||
/// Make a random permutation of the state, and of the edges
|
||||
/// leaving this state.
|
||||
SPOT_API void
|
||||
randomize(twa_graph_ptr& aut,
|
||||
bool randomize_states = true,
|
||||
bool randomize_edges = true);
|
||||
}
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2011, 2013, 2014, 2015 Laboratoire de Recherche
|
||||
// et Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 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 <cassert>
|
||||
#include "reachiter.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
// tgba_reachable_iterator
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
tgba_reachable_iterator::tgba_reachable_iterator(const const_twa_ptr& a)
|
||||
: aut_(a)
|
||||
{
|
||||
}
|
||||
|
||||
tgba_reachable_iterator::~tgba_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;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator::run()
|
||||
{
|
||||
int n = 0;
|
||||
start();
|
||||
const state* i = aut_->get_init_state();
|
||||
if (want_state(i))
|
||||
add_state(i);
|
||||
seen[i] = ++n;
|
||||
const state* t;
|
||||
while ((t = next_state()))
|
||||
{
|
||||
assert(seen.find(t) != seen.end());
|
||||
int tn = seen[t];
|
||||
twa_succ_iterator* si = aut_->succ_iter(t);
|
||||
process_state(t, tn, si);
|
||||
if (si->first())
|
||||
do
|
||||
{
|
||||
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(t, tn, current, n, si);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ws)
|
||||
process_link(t, tn, s->first, s->second, si);
|
||||
current->destroy();
|
||||
}
|
||||
}
|
||||
while (si->next());
|
||||
aut_->release_iter(si);
|
||||
}
|
||||
end();
|
||||
}
|
||||
|
||||
bool
|
||||
tgba_reachable_iterator::want_state(const state*) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator::start()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator::end()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator::process_state(const state*, int,
|
||||
twa_succ_iterator*)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator::process_link(const state*, int,
|
||||
const state*, int,
|
||||
const twa_succ_iterator*)
|
||||
{
|
||||
}
|
||||
|
||||
// tgba_reachable_iterator_breadth_first
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
tgba_reachable_iterator_breadth_first::
|
||||
tgba_reachable_iterator_breadth_first(const const_twa_ptr& a)
|
||||
: tgba_reachable_iterator(a)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_breadth_first::add_state(const state* s)
|
||||
{
|
||||
todo.push_back(s);
|
||||
}
|
||||
|
||||
const state*
|
||||
tgba_reachable_iterator_breadth_first::next_state()
|
||||
{
|
||||
if (todo.empty())
|
||||
return nullptr;
|
||||
const state* s = todo.front();
|
||||
todo.pop_front();
|
||||
return s;
|
||||
}
|
||||
|
||||
// tgba_reachable_iterator_depth_first
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
tgba_reachable_iterator_depth_first::
|
||||
tgba_reachable_iterator_depth_first(const const_twa_ptr& a)
|
||||
: aut_(a)
|
||||
{
|
||||
}
|
||||
|
||||
tgba_reachable_iterator_depth_first::~tgba_reachable_iterator_depth_first()
|
||||
{
|
||||
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;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first::push(const state* s, int sn)
|
||||
{
|
||||
twa_succ_iterator* si = aut_->succ_iter(s);
|
||||
process_state(s, sn, si);
|
||||
stack_item item = { s, sn, si };
|
||||
todo.push_back(item);
|
||||
si->first();
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first::pop()
|
||||
{
|
||||
aut_->release_iter(todo.back().it);
|
||||
todo.pop_back();
|
||||
if (!todo.empty())
|
||||
todo.back().it->next();
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first::run()
|
||||
{
|
||||
int n = 1;
|
||||
start();
|
||||
const state* i = aut_->get_init_state();
|
||||
if (want_state(i))
|
||||
push(i, n);
|
||||
seen[i] = n++;
|
||||
const state* dst;
|
||||
while (!todo.empty())
|
||||
{
|
||||
twa_succ_iterator* si = todo.back().it;
|
||||
if (si->done())
|
||||
{
|
||||
pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
dst = si->dst();
|
||||
auto res = seen.emplace(dst, n);
|
||||
if (!res.second)
|
||||
{
|
||||
// The state has already been seen.
|
||||
dst->destroy();
|
||||
// 0-numbered states are not wanted.
|
||||
if (res.first->second == 0)
|
||||
{
|
||||
si->next();
|
||||
continue;
|
||||
}
|
||||
dst = res.first->first;
|
||||
}
|
||||
else if (!want_state(dst))
|
||||
{
|
||||
// Mark this state as non-wanted in case we see it again.
|
||||
res.first->second = 0;
|
||||
si->next();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
++n;
|
||||
}
|
||||
|
||||
int dst_n = res.first->second;
|
||||
process_link(todo.back().src, todo.back().src_n, dst, dst_n, si);
|
||||
|
||||
if (res.second)
|
||||
push(dst, dst_n);
|
||||
else
|
||||
si->next();
|
||||
}
|
||||
end();
|
||||
}
|
||||
|
||||
bool
|
||||
tgba_reachable_iterator_depth_first::want_state(const state*) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first::start()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first::end()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first::process_state(const state*, int,
|
||||
twa_succ_iterator*)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first::process_link(const state*, int,
|
||||
const state*, int,
|
||||
const twa_succ_iterator*)
|
||||
{
|
||||
}
|
||||
|
||||
// tgba_reachable_iterator_depth_first_stack
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
tgba_reachable_iterator_depth_first_stack::
|
||||
tgba_reachable_iterator_depth_first_stack(const const_twa_ptr& a)
|
||||
: tgba_reachable_iterator_depth_first(a)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first_stack::push(const state* s, int sn)
|
||||
{
|
||||
stack_.insert(sn);
|
||||
this->tgba_reachable_iterator_depth_first::push(s, sn);
|
||||
}
|
||||
|
||||
void
|
||||
tgba_reachable_iterator_depth_first_stack::pop()
|
||||
{
|
||||
stack_.erase(todo.back().src_n);
|
||||
this->tgba_reachable_iterator_depth_first::pop();
|
||||
}
|
||||
|
||||
bool
|
||||
tgba_reachable_iterator_depth_first_stack::on_stack(int sn) const
|
||||
{
|
||||
return stack_.find(sn) != stack_.end();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2009, 2011, 2013 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2003, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "misc/hash.hh"
|
||||
#include "twa/twa.hh"
|
||||
#include <stack>
|
||||
#include <deque>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \ingroup twa_generic
|
||||
/// \brief Iterate over all reachable states of a spot::tgba.
|
||||
class SPOT_API tgba_reachable_iterator
|
||||
{
|
||||
public:
|
||||
tgba_reachable_iterator(const const_twa_ptr& a);
|
||||
virtual ~tgba_reachable_iterator();
|
||||
|
||||
/// \brief Iterate over all reachable states of a spot::tgba.
|
||||
///
|
||||
/// This is a template method that will call add_state(),
|
||||
/// next_state(), want_state(), start(), end(), process_state(),
|
||||
/// and process_link(), while it iterates over states.
|
||||
virtual void run();
|
||||
|
||||
/// \name Todo list management.
|
||||
///
|
||||
/// See e.g.
|
||||
/// spot::tgba_reachable_iterator_breadth_first for 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.
|
||||
/// \param si The spot::twa_succ_iterator for \a s.
|
||||
virtual void process_state(const state* s, int n, twa_succ_iterator* si);
|
||||
/// Called by run() to process a transition.
|
||||
///
|
||||
/// \param in_s The source state
|
||||
/// \param in The source state number.
|
||||
/// \param out_s The destination state
|
||||
/// \param out The destination state number.
|
||||
/// \param si The spot::twa_succ_iterator positionned on the current
|
||||
/// transition.
|
||||
///
|
||||
/// The in_s and out_s states are owned by the
|
||||
/// spot::tgba_reachable_iterator instance and destroyed when the
|
||||
/// instance is destroyed.
|
||||
virtual void process_link(const state* in_s, int in,
|
||||
const state* out_s, int out,
|
||||
const twa_succ_iterator* si);
|
||||
|
||||
protected:
|
||||
const_twa_ptr aut_; ///< The spot::tgba to explore.
|
||||
|
||||
typedef std::unordered_map<const state*, int,
|
||||
state_ptr_hash, state_ptr_equal> seen_map;
|
||||
seen_map seen; ///< States already seen.
|
||||
};
|
||||
|
||||
/// \ingroup twa_generic
|
||||
/// \brief An implementation of spot::tgba_reachable_iterator that browses
|
||||
/// states breadth first.
|
||||
class SPOT_API tgba_reachable_iterator_breadth_first :
|
||||
public tgba_reachable_iterator
|
||||
{
|
||||
public:
|
||||
tgba_reachable_iterator_breadth_first(const const_twa_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.
|
||||
};
|
||||
|
||||
/// \ingroup twa_generic
|
||||
/// \brief Iterate over all states of an automaton using a DFS.
|
||||
class SPOT_API tgba_reachable_iterator_depth_first
|
||||
{
|
||||
public:
|
||||
tgba_reachable_iterator_depth_first(const const_twa_ptr& a);
|
||||
virtual ~tgba_reachable_iterator_depth_first();
|
||||
|
||||
/// \brief Iterate over all reachable states of a spot::tgba.
|
||||
///
|
||||
/// This is a template method that will call start(), end(),
|
||||
/// want_state(), process_state(), and process_link(), while it
|
||||
/// iterates over states.
|
||||
virtual void run();
|
||||
|
||||
/// 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.
|
||||
/// \param si The spot::twa_succ_iterator for \a s.
|
||||
virtual void process_state(const state* s, int n, twa_succ_iterator* si);
|
||||
/// Called by run() to process a transition.
|
||||
///
|
||||
/// \param in_s The source state
|
||||
/// \param in The source state number.
|
||||
/// \param out_s The destination state
|
||||
/// \param out The destination state number.
|
||||
/// \param si The spot::twa_succ_iterator positionned on the current
|
||||
/// transition.
|
||||
///
|
||||
/// The in_s and out_s states are owned by the
|
||||
/// spot::tgba_reachable_iterator instance and destroyed when the
|
||||
/// instance is destroyed.
|
||||
virtual void process_link(const state* in_s, int in,
|
||||
const state* out_s, int out,
|
||||
const twa_succ_iterator* si);
|
||||
|
||||
protected:
|
||||
const_twa_ptr aut_; ///< The spot::tgba to explore.
|
||||
|
||||
typedef std::unordered_map<const state*, int,
|
||||
state_ptr_hash, state_ptr_equal> seen_map;
|
||||
seen_map seen; ///< States already seen.
|
||||
struct stack_item
|
||||
{
|
||||
const state* src;
|
||||
int src_n;
|
||||
twa_succ_iterator* it;
|
||||
};
|
||||
std::deque<stack_item> todo; ///< the DFS stack
|
||||
|
||||
/// Push a new state in todo.
|
||||
virtual void push(const state* s, int sn);
|
||||
/// Pop the DFS stack.
|
||||
virtual void pop();
|
||||
};
|
||||
|
||||
/// \ingroup twa_generic
|
||||
/// \brief Iterate over all states of an automaton using a DFS.
|
||||
///
|
||||
/// This variant also maintains a set of states that are on the DFS
|
||||
/// stack. It can be checked using the on_stack() method.
|
||||
class tgba_reachable_iterator_depth_first_stack
|
||||
: public tgba_reachable_iterator_depth_first
|
||||
{
|
||||
public:
|
||||
tgba_reachable_iterator_depth_first_stack(const const_twa_ptr& a);
|
||||
/// \brief Whether state sn is on the DFS stack.
|
||||
///
|
||||
/// Note the destination state of a transition is only pushed to
|
||||
/// the stack after process_link() has been called.
|
||||
bool on_stack(int sn) const;
|
||||
protected:
|
||||
virtual void push(const state* s, int sn);
|
||||
virtual void pop();
|
||||
|
||||
std::unordered_set<int> stack_;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "relabel.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
void
|
||||
relabel_here(twa_graph_ptr& aut, relabeling_map* relmap)
|
||||
{
|
||||
bddPair* pairs = bdd_newpair();
|
||||
auto d = aut->get_dict();
|
||||
std::vector<int> vars;
|
||||
vars.reserve(relmap->size());
|
||||
for (auto& p: *relmap)
|
||||
{
|
||||
int oldv = aut->register_ap(p.first);
|
||||
int newv = aut->register_ap(p.second);
|
||||
bdd_setpair(pairs, oldv, newv);
|
||||
vars.push_back(oldv);
|
||||
}
|
||||
for (auto& t: aut->edges())
|
||||
t.cond = bdd_replace(t.cond, pairs);
|
||||
for (auto v: vars)
|
||||
d->unregister_variable(v, aut);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "tl/relabel.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// replace atomic propositions in an automaton
|
||||
SPOT_API void
|
||||
relabel_here(twa_graph_ptr& aut,
|
||||
relabeling_map* relmap);
|
||||
}
|
||||
|
|
@ -1,758 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "remfin.hh"
|
||||
#include "sccinfo.hh"
|
||||
#include <iostream>
|
||||
#include "cleanacc.hh"
|
||||
#include "totgba.hh"
|
||||
#include "isdet.hh"
|
||||
#include "mask.hh"
|
||||
|
||||
//#define TRACE
|
||||
#ifdef TRACE
|
||||
#define trace std::cerr
|
||||
#else
|
||||
#define trace while (0) std::cerr
|
||||
#endif
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Check whether the SCC composed of all states STATES, and
|
||||
// visiting all acceptance marks in SETS contains non-accepting
|
||||
// cycles.
|
||||
//
|
||||
// A cycle is accepting (in a Rabin automaton) if there exists an
|
||||
// acceptance pair (Fᵢ, Iᵢ) such that some states from Iᵢ are
|
||||
// visited while no states from Fᵢ are visited.
|
||||
//
|
||||
// Consequently, a cycle is non-accepting if for all acceptance
|
||||
// pairs (Fᵢ, Iᵢ), either no states from Iᵢ are visited or some
|
||||
// states from Fᵢ are visited. (This corresponds to an accepting
|
||||
// cycle with Streett acceptance.)
|
||||
static bool
|
||||
is_scc_ba_type(const const_twa_graph_ptr& aut,
|
||||
const std::vector<unsigned>& states,
|
||||
std::vector<bool>& final,
|
||||
acc_cond::mark_t inf_pairs,
|
||||
acc_cond::mark_t inf_alone,
|
||||
acc_cond::mark_t sets)
|
||||
{
|
||||
// Consider the SCC as one large cycle and check its
|
||||
// intersection with all Fᵢs and Iᵢs: This is the SETS
|
||||
// variable.
|
||||
//
|
||||
// Let f=[F₁,F₂,...] and i=[I₁,I₂,...] be bitvectors where bit
|
||||
// Fᵢ (resp. Iᵢ) indicates that Fᵢ (resp. Iᵢ) has been visited
|
||||
// in the SCC.
|
||||
acc_cond::mark_t f = (sets << 1) & inf_pairs;
|
||||
acc_cond::mark_t i = sets & inf_pairs;
|
||||
// If we have i&!f = [0,0,...] that means that the cycle formed
|
||||
// by the entire SCC is not accepting. However that does not
|
||||
// necessarily imply that all cycles in the SCC are also
|
||||
// non-accepting. We may have a smaller cycle that is
|
||||
// accepting, but which becomes non-accepting when extended with
|
||||
// more states.
|
||||
i -= f;
|
||||
i |= (inf_alone & sets);
|
||||
if (!i)
|
||||
{
|
||||
// Check whether the SCC is accepting. We do that by simply
|
||||
// converting that SCC into a TGBA and running our emptiness
|
||||
// check. This is not a really smart implementation and
|
||||
// could be improved.
|
||||
std::vector<bool> keep(aut->num_states(), false);
|
||||
for (auto s: states)
|
||||
keep[s] = true;
|
||||
auto sccaut = mask_keep_states(aut, keep, states.front());
|
||||
// Force SBA to false. It does not affect the emptiness
|
||||
// check result, however it prevent recurring into this
|
||||
// procedure, because empty() will call to_tgba() wich will
|
||||
// call remove_fin()...
|
||||
sccaut->prop_state_acc(false);
|
||||
// If SCCAUT is empty, the SCC is BA-type (and none
|
||||
// of its states are final). If SCCAUT is nonempty, the SCC
|
||||
// is not BA type.
|
||||
return sccaut->is_empty();
|
||||
}
|
||||
// The bits remaining sets in i corresponds to I₁s that have
|
||||
// been seen with seeing the mathing F₁. In this SCC any state
|
||||
// in these I₁ is therefore final. Otherwise we do not know: it
|
||||
// is possible that there is a non-accepting cycle in the SCC
|
||||
// that do not visit Fᵢ.
|
||||
std::set<unsigned> unknown;
|
||||
for (auto s: states)
|
||||
{
|
||||
if (aut->state_acc_sets(s) & i)
|
||||
final[s] = true;
|
||||
else
|
||||
unknown.insert(s);
|
||||
}
|
||||
// Check whether it is possible to build non-accepting cycles
|
||||
// using only the "unknown" states.
|
||||
while (!unknown.empty())
|
||||
{
|
||||
std::vector<bool> keep(aut->num_states(), false);
|
||||
for (auto s: unknown)
|
||||
keep[s] = true;
|
||||
|
||||
scc_info si(mask_keep_states(aut, keep, *unknown.begin()));
|
||||
unsigned scc_max = si.scc_count();
|
||||
for (unsigned scc = 0; scc < scc_max; ++scc)
|
||||
{
|
||||
for (auto s: si.states_of(scc))
|
||||
unknown.erase(s);
|
||||
if (si.is_rejecting_scc(scc)) // this includes trivial SCCs
|
||||
continue;
|
||||
if (!is_scc_ba_type(aut, si.states_of(scc),
|
||||
final, inf_pairs, 0U, si.acc(scc)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Specialized conversion from Rabin acceptance to Büchi acceptance.
|
||||
// Is able to detect SCCs that are Büchi-type (i.e., they can be
|
||||
// converted to Büchi acceptance without chaning their structure).
|
||||
// Currently only work with state-based acceptance.
|
||||
//
|
||||
// See "Deterministic ω-automata vis-a-vis Deterministic Büchi
|
||||
// Automata", S. Krishnan, A. Puri, and R. Brayton (ISAAC'94) for
|
||||
// some details about detecting Büchi-typeness.
|
||||
//
|
||||
// We essentially apply this method SCC-wise. The paper is
|
||||
// concerned about *deterministic* automata, but we apply the
|
||||
// algorithm on non-deterministic automata as well: in the worst
|
||||
// case it is possible that a Büchi-type SCC with some
|
||||
// non-deterministic has one accepting and one rejecting run for
|
||||
// the same word. In this case we may fail to detect the
|
||||
// Büchi-typeness of the SCC, but the resulting automaton should
|
||||
// be correct nonetheless.
|
||||
static twa_graph_ptr
|
||||
ra_to_ba(const const_twa_graph_ptr& aut,
|
||||
acc_cond::mark_t inf_pairs,
|
||||
acc_cond::mark_t inf_alone,
|
||||
acc_cond::mark_t fin_alone)
|
||||
{
|
||||
assert(aut->prop_state_acc());
|
||||
|
||||
scc_info si(aut);
|
||||
// For state-based Rabin automata, we check each SCC for
|
||||
// BA-typeness. If an SCC is BA-type, its final states are stored
|
||||
// in BA_FINAL_STATES.
|
||||
std::vector<bool> scc_is_ba_type(si.scc_count(), false);
|
||||
bool ba_type = false;
|
||||
std::vector<bool> ba_final_states;
|
||||
|
||||
#ifdef DEBUG
|
||||
acc_cond::mark_t fin;
|
||||
acc_cond::mark_t inf;
|
||||
std::tie(inf, fin) = aut->get_acceptance().used_inf_fin_sets();
|
||||
assert(inf == (inf_pairs | inf_alone));
|
||||
assert(fin == ((inf_pairs >> 1) | fin_alone));
|
||||
#endif
|
||||
ba_final_states.resize(aut->num_states(), false);
|
||||
ba_type = true; // until proven otherwise
|
||||
unsigned scc_max = si.scc_count();
|
||||
for (unsigned scc = 0; scc < scc_max; ++scc)
|
||||
{
|
||||
if (si.is_rejecting_scc(scc)) // this includes trivial SCCs
|
||||
{
|
||||
scc_is_ba_type[scc] = true;
|
||||
continue;
|
||||
}
|
||||
bool scc_ba_type = false;
|
||||
auto sets = si.acc(scc);
|
||||
// If there is one fin_alone that is not in the SCC,
|
||||
// any cycle in the SCC is accepting. Mark all states
|
||||
// as final.
|
||||
if ((sets & fin_alone) != fin_alone)
|
||||
{
|
||||
for (auto s: si.states_of(scc))
|
||||
ba_final_states[s] = true;
|
||||
scc_ba_type = true;
|
||||
}
|
||||
// Conversely, if all fin_alone appear in the SCC, then it
|
||||
// cannot be accepting.
|
||||
else if (sets & fin_alone)
|
||||
{
|
||||
scc_ba_type = false;
|
||||
}
|
||||
// In the generale case (no fin_alone involved), we need
|
||||
// a dedicated check.
|
||||
else
|
||||
{
|
||||
scc_ba_type = is_scc_ba_type(aut, si.states_of(scc),
|
||||
ba_final_states,
|
||||
inf_pairs, inf_alone, si.acc(scc));
|
||||
}
|
||||
ba_type &= scc_ba_type;
|
||||
scc_is_ba_type[scc] = scc_ba_type;
|
||||
}
|
||||
|
||||
#ifdef TRACE
|
||||
trace << "SCC DBA-realizibility\n";
|
||||
for (unsigned scc = 0; scc < scc_max; ++scc)
|
||||
{
|
||||
trace << scc << ": " << scc_is_ba_type[scc] << " {";
|
||||
for (auto s: si.states_of(scc))
|
||||
trace << ' ' << s;
|
||||
trace << " }\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned nst = aut->num_states();
|
||||
auto res = make_twa_graph(aut->get_dict());
|
||||
res->copy_ap_of(aut);
|
||||
res->prop_copy(aut, { true, false, false, true });
|
||||
res->new_states(nst);
|
||||
res->set_buchi();
|
||||
res->set_init_state(aut->get_init_state_number());
|
||||
bool deterministic = aut->prop_deterministic();
|
||||
|
||||
std::vector<unsigned> state_map(aut->num_states());
|
||||
for (unsigned n = 0; n < scc_max; ++n)
|
||||
{
|
||||
auto states = si.states_of(n);
|
||||
|
||||
if (scc_is_ba_type[n])
|
||||
{
|
||||
// If the SCC is BA-type, we know exactly what state need to
|
||||
// be marked as accepting.
|
||||
for (auto s: states)
|
||||
{
|
||||
bool acc = ba_final_states[s];
|
||||
for (auto& t: aut->out(s))
|
||||
res->new_acc_edge(s, t.dst, t.cond, acc);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
deterministic = false;
|
||||
|
||||
// The main copy is only accepting for inf_alone
|
||||
// and for all Inf sets that have no matching Fin
|
||||
// sets in this SCC.
|
||||
acc_cond::mark_t sccsets = si.acc(n);
|
||||
acc_cond::mark_t f = (sccsets << 1) & inf_pairs;
|
||||
acc_cond::mark_t i = sccsets & (inf_pairs | inf_alone);
|
||||
i -= f;
|
||||
for (auto s: states)
|
||||
{
|
||||
bool acc = aut->state_acc_sets(s) & i;
|
||||
for (auto& t: aut->out(s))
|
||||
res->new_acc_edge(s, t.dst, t.cond, acc);
|
||||
}
|
||||
|
||||
auto rem = sccsets & ((inf_pairs >> 1) | fin_alone);
|
||||
assert(rem != 0U);
|
||||
auto sets = rem.sets();
|
||||
|
||||
unsigned ss = states.size();
|
||||
|
||||
for (auto r: sets)
|
||||
{
|
||||
unsigned base = res->new_states(ss);
|
||||
for (auto s: states)
|
||||
state_map[s] = base++;
|
||||
for (auto s: states)
|
||||
{
|
||||
auto ns = state_map[s];
|
||||
acc_cond::mark_t acc = aut->state_acc_sets(s);
|
||||
if (acc.has(r))
|
||||
continue;
|
||||
bool jacc = acc & inf_alone;
|
||||
bool cacc = fin_alone.has(r) || acc.has(r + 1);
|
||||
for (auto& t: aut->out(s))
|
||||
{
|
||||
if (si.scc_of(t.dst) != n)
|
||||
continue;
|
||||
auto nd = state_map[t.dst];
|
||||
res->new_acc_edge(ns, nd, t.cond, cacc);
|
||||
// We need only one non-deterministic jump per
|
||||
// cycle. As an approximation, we only do
|
||||
// them on back-links.
|
||||
if (t.dst <= s)
|
||||
res->new_acc_edge(s, nd, t.cond, jacc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res->purge_dead_states();
|
||||
res->prop_deterministic(deterministic);
|
||||
return res;
|
||||
}
|
||||
|
||||
static twa_graph_ptr
|
||||
rabin_to_buchi_maybe(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
if (!aut->prop_state_acc())
|
||||
return nullptr;
|
||||
|
||||
auto code = aut->get_acceptance();
|
||||
|
||||
if (code.is_tt())
|
||||
return nullptr;
|
||||
|
||||
acc_cond::mark_t inf_pairs = 0U;
|
||||
acc_cond::mark_t inf_alone = 0U;
|
||||
acc_cond::mark_t fin_alone = 0U;
|
||||
|
||||
auto s = code.back().size;
|
||||
|
||||
// Rabin 1
|
||||
if (code.back().op == acc_cond::acc_op::And && s == 4)
|
||||
goto start_and;
|
||||
// Co-Büchi
|
||||
else if (code.back().op == acc_cond::acc_op::Fin && s == 1)
|
||||
goto start_fin;
|
||||
// Rabin >1
|
||||
else if (code.back().op != acc_cond::acc_op::Or)
|
||||
return nullptr;
|
||||
|
||||
while (s)
|
||||
{
|
||||
--s;
|
||||
if (code[s].op == acc_cond::acc_op::And)
|
||||
{
|
||||
start_and:
|
||||
auto o1 = code[--s].op;
|
||||
auto m1 = code[--s].mark;
|
||||
auto o2 = code[--s].op;
|
||||
auto m2 = code[--s].mark;
|
||||
// We expect
|
||||
// Fin({n}) & Inf({n+1})
|
||||
if (o1 != acc_cond::acc_op::Fin ||
|
||||
o2 != acc_cond::acc_op::Inf ||
|
||||
m1.count() != 1 ||
|
||||
m2.count() != 1 ||
|
||||
m2 != (m1 << 1))
|
||||
return nullptr;
|
||||
inf_pairs |= m2;
|
||||
}
|
||||
else if (code[s].op == acc_cond::acc_op::Fin)
|
||||
{
|
||||
start_fin:
|
||||
fin_alone |= code[--s].mark;
|
||||
}
|
||||
else if (code[s].op == acc_cond::acc_op::Inf)
|
||||
{
|
||||
auto m1 = code[--s].mark;
|
||||
if (m1.count() != 1)
|
||||
return nullptr;
|
||||
inf_alone |= m1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
trace << "inf_pairs: " << inf_pairs << '\n';
|
||||
trace << "inf_alone: " << inf_alone << '\n';
|
||||
trace << "fin_alone: " << fin_alone << '\n';
|
||||
return ra_to_ba(aut, inf_pairs, inf_alone, fin_alone);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// If the DNF is
|
||||
// Fin(1)&Inf(2)&Inf(4) | Fin(2)&Fin(3)&Inf(1) |
|
||||
// Inf(1)&Inf(3) | Inf(1)&Inf(2) | Fin(4)
|
||||
// this returns the following map:
|
||||
// {1} => Inf(2)&Inf(4)
|
||||
// {2,3} => Inf(1)
|
||||
// {} => Inf(1)&Inf(3) | Inf(1)&Inf(2)
|
||||
// {4} => t
|
||||
static std::map<acc_cond::mark_t, acc_cond::acc_code>
|
||||
split_dnf_acc_by_fin(const acc_cond::acc_code& acc)
|
||||
{
|
||||
std::map<acc_cond::mark_t, acc_cond::acc_code> res;
|
||||
auto pos = &acc.back();
|
||||
if (pos->op == acc_cond::acc_op::Or)
|
||||
--pos;
|
||||
auto start = &acc.front();
|
||||
while (pos > start)
|
||||
{
|
||||
if (pos->op == acc_cond::acc_op::Fin)
|
||||
{
|
||||
// We have only a Fin term, without Inf. In this case
|
||||
// only, the Fin() may encode a disjunction of sets.
|
||||
for (auto s: pos[-1].mark.sets())
|
||||
{
|
||||
acc_cond::mark_t fin = 0U;
|
||||
fin.set(s);
|
||||
res[fin] = acc_cond::acc_code{};
|
||||
}
|
||||
pos -= pos->size + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have a conjunction of Fin and Inf sets.
|
||||
auto end = pos - pos->size - 1;
|
||||
acc_cond::mark_t fin = 0U;
|
||||
acc_cond::mark_t inf = 0U;
|
||||
while (pos > end)
|
||||
{
|
||||
switch (pos->op)
|
||||
{
|
||||
case acc_cond::acc_op::And:
|
||||
--pos;
|
||||
break;
|
||||
case acc_cond::acc_op::Fin:
|
||||
fin |= pos[-1].mark;
|
||||
assert(pos[-1].mark.count() == 1);
|
||||
pos -= 2;
|
||||
break;
|
||||
case acc_cond::acc_op::Inf:
|
||||
inf |= pos[-1].mark;
|
||||
pos -= 2;
|
||||
break;
|
||||
case acc_cond::acc_op::FinNeg:
|
||||
case acc_cond::acc_op::InfNeg:
|
||||
case acc_cond::acc_op::Or:
|
||||
SPOT_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(pos == end);
|
||||
acc_cond::acc_word w[2];
|
||||
w[0].mark = inf;
|
||||
w[1].op = acc_cond::acc_op::Inf;
|
||||
w[1].size = 1;
|
||||
acc_cond::acc_code c;
|
||||
c.insert(c.end(), w, w + 2);
|
||||
auto p = res.emplace(fin, c);
|
||||
if (!p.second)
|
||||
p.first->second.append_or(std::move(c));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static twa_graph_ptr
|
||||
remove_fin_det_weak(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
// Clone the original automaton.
|
||||
auto res = make_twa_graph(aut,
|
||||
{
|
||||
true, // state based
|
||||
true, // inherently weak
|
||||
true, // determinisitic
|
||||
true, // stutter inv.
|
||||
});
|
||||
res->purge_dead_states();
|
||||
scc_info si(res);
|
||||
|
||||
// We will modify res in place, and the resulting
|
||||
// automaton will only have one acceptance set.
|
||||
acc_cond::mark_t all_acc = res->set_buchi();
|
||||
res->prop_state_acc(true);
|
||||
res->prop_deterministic(true);
|
||||
|
||||
unsigned sink = res->num_states();
|
||||
for (unsigned src = 0; src < sink; ++src)
|
||||
{
|
||||
acc_cond::mark_t acc = 0U;
|
||||
unsigned scc = si.scc_of(src);
|
||||
if (si.is_accepting_scc(scc) && !si.is_trivial(scc))
|
||||
acc = all_acc;
|
||||
// Keep track of all conditions on edge leaving state
|
||||
// SRC, so we can complete it.
|
||||
bdd missingcond = bddtrue;
|
||||
for (auto& t: res->out(src))
|
||||
{
|
||||
missingcond -= t.cond;
|
||||
t.acc = acc;
|
||||
}
|
||||
// Complete the original automaton.
|
||||
if (missingcond != bddfalse)
|
||||
{
|
||||
if (res->num_states() == sink)
|
||||
{
|
||||
res->new_state();
|
||||
res->new_acc_edge(sink, sink, bddtrue);
|
||||
}
|
||||
res->new_edge(src, sink, missingcond);
|
||||
}
|
||||
}
|
||||
//res->merge_edges();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr remove_fin(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
if (!aut->acc().uses_fin_acceptance())
|
||||
return std::const_pointer_cast<twa_graph>(aut);
|
||||
|
||||
// FIXME: we should check whether the automaton is weak.
|
||||
if (aut->prop_inherently_weak() && is_deterministic(aut))
|
||||
return remove_fin_det_weak(aut);
|
||||
|
||||
if (auto maybe = streett_to_generalized_buchi_maybe(aut))
|
||||
return maybe;
|
||||
|
||||
if (auto maybe = rabin_to_buchi_maybe(aut))
|
||||
return maybe;
|
||||
|
||||
std::vector<acc_cond::acc_code> code;
|
||||
std::vector<acc_cond::mark_t> rem;
|
||||
std::vector<acc_cond::mark_t> keep;
|
||||
std::vector<acc_cond::mark_t> add;
|
||||
bool has_true_term = false;
|
||||
acc_cond::mark_t allinf = 0U;
|
||||
acc_cond::mark_t allfin = 0U;
|
||||
{
|
||||
auto acccode = aut->get_acceptance();
|
||||
if (!acccode.is_dnf())
|
||||
acccode = acccode.to_dnf();
|
||||
|
||||
auto split = split_dnf_acc_by_fin(acccode);
|
||||
|
||||
auto sz = split.size();
|
||||
assert(sz > 0);
|
||||
|
||||
rem.reserve(sz);
|
||||
code.reserve(sz);
|
||||
keep.reserve(sz);
|
||||
add.reserve(sz);
|
||||
for (auto p: split)
|
||||
{
|
||||
// The empty Fin should always come first
|
||||
assert(p.first != 0U || rem.empty());
|
||||
rem.push_back(p.first);
|
||||
allfin |= p.first;
|
||||
acc_cond::mark_t inf = 0U;
|
||||
if (!p.second.empty())
|
||||
{
|
||||
auto pos = &p.second.back();
|
||||
auto end = &p.second.front();
|
||||
while (pos > end)
|
||||
{
|
||||
switch (pos->op)
|
||||
{
|
||||
case acc_cond::acc_op::And:
|
||||
case acc_cond::acc_op::Or:
|
||||
--pos;
|
||||
break;
|
||||
case acc_cond::acc_op::Inf:
|
||||
inf |= pos[-1].mark;
|
||||
pos -= 2;
|
||||
break;
|
||||
case acc_cond::acc_op::Fin:
|
||||
case acc_cond::acc_op::FinNeg:
|
||||
case acc_cond::acc_op::InfNeg:
|
||||
SPOT_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inf == 0U)
|
||||
{
|
||||
has_true_term = true;
|
||||
}
|
||||
code.push_back(std::move(p.second));
|
||||
keep.push_back(inf);
|
||||
allinf |= inf;
|
||||
add.push_back(0U);
|
||||
}
|
||||
}
|
||||
assert(add.size() > 0);
|
||||
|
||||
acc_cond acc = aut->acc();
|
||||
unsigned extra_sets = 0;
|
||||
|
||||
// Do we have common sets between the acceptance terms?
|
||||
// If so, we need extra sets to distinguish the terms.
|
||||
bool interference = false;
|
||||
{
|
||||
auto sz = keep.size();
|
||||
acc_cond::mark_t sofar = 0U;
|
||||
for (unsigned i = 0; i < sz; ++i)
|
||||
{
|
||||
auto k = keep[i];
|
||||
if (k & sofar)
|
||||
{
|
||||
interference = true;
|
||||
break;
|
||||
}
|
||||
sofar |= k;
|
||||
}
|
||||
if (interference)
|
||||
{
|
||||
trace << "We have interferences\n";
|
||||
// We need extra set, but we will try
|
||||
// to reuse the Fin number if they are
|
||||
// not used as Inf as well.
|
||||
std::vector<int> exs(acc.num_sets());
|
||||
for (auto f: allfin.sets())
|
||||
{
|
||||
if (allinf.has(f)) // Already used as Inf
|
||||
{
|
||||
exs[f] = acc.add_set();
|
||||
++extra_sets;
|
||||
}
|
||||
else
|
||||
{
|
||||
exs[f] = f;
|
||||
}
|
||||
}
|
||||
for (unsigned i = 0; i < sz; ++i)
|
||||
{
|
||||
acc_cond::mark_t m = 0U;
|
||||
for (auto f: rem[i].sets())
|
||||
m.set(exs[f]);
|
||||
trace << "rem[" << i << "] = " << rem[i]
|
||||
<< " m = " << m << '\n';
|
||||
add[i] = m;
|
||||
code[i].append_and(acc.inf(m));
|
||||
trace << "code[" << i << "] = " << code[i] << '\n';
|
||||
}
|
||||
}
|
||||
else if (has_true_term)
|
||||
{
|
||||
trace << "We have a true term\n";
|
||||
unsigned one = acc.add_sets(1);
|
||||
extra_sets += 1;
|
||||
auto m = acc.marks({one});
|
||||
auto c = acc.inf(m);
|
||||
for (unsigned i = 0; i < sz; ++i)
|
||||
{
|
||||
if (!code[i].is_tt())
|
||||
continue;
|
||||
add[i] = m;
|
||||
code[i].append_and(c);
|
||||
c = acc.fin(0U); // Use false for the other terms.
|
||||
trace << "code[" << i << "] = " << code[i] << '\n';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
acc_cond::acc_code new_code = aut->acc().fin(0U);
|
||||
for (auto c: code)
|
||||
new_code.append_or(std::move(c));
|
||||
|
||||
unsigned cs = code.size();
|
||||
for (unsigned i = 0; i < cs; ++i)
|
||||
trace << i << " Rem " << rem[i] << " Code " << code[i]
|
||||
<< " Keep " << keep[i] << '\n';
|
||||
|
||||
unsigned nst = aut->num_states();
|
||||
auto res = make_twa_graph(aut->get_dict());
|
||||
res->copy_ap_of(aut);
|
||||
res->prop_copy(aut, { true, false, false, true });
|
||||
res->new_states(nst);
|
||||
res->set_acceptance(aut->num_sets() + extra_sets, new_code);
|
||||
res->set_init_state(aut->get_init_state_number());
|
||||
|
||||
bool sbacc = aut->prop_state_acc();
|
||||
scc_info si(aut);
|
||||
unsigned nscc = si.scc_count();
|
||||
std::vector<unsigned> state_map(nst);
|
||||
for (unsigned n = 0; n < nscc; ++n)
|
||||
{
|
||||
auto m = si.acc(n);
|
||||
auto states = si.states_of(n);
|
||||
trace << "SCC #" << n << " uses " << m << '\n';
|
||||
|
||||
// What to keep and add into the main copy
|
||||
acc_cond::mark_t main_sets = 0U;
|
||||
acc_cond::mark_t main_add = 0U;
|
||||
bool intersects_fin = false;
|
||||
for (unsigned i = 0; i < cs; ++i)
|
||||
if (!(m & rem[i]))
|
||||
{
|
||||
main_sets |= keep[i];
|
||||
main_add |= add[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
intersects_fin = true;
|
||||
}
|
||||
trace << "main_sets " << main_sets << "\nmain_add " << main_add << '\n';
|
||||
|
||||
// Create the main copy
|
||||
for (auto s: states)
|
||||
for (auto& t: aut->out(s))
|
||||
{
|
||||
acc_cond::mark_t a = 0U;
|
||||
if (sbacc || SPOT_LIKELY(si.scc_of(t.dst) == n))
|
||||
a = (t.acc & main_sets) | main_add;
|
||||
res->new_edge(s, t.dst, t.cond, a);
|
||||
}
|
||||
|
||||
// We do not need any other copy if the SCC is non-accepting,
|
||||
// of if it does not intersect any Fin.
|
||||
if (!intersects_fin || si.is_rejecting_scc(n))
|
||||
continue;
|
||||
|
||||
// Create clones
|
||||
for (unsigned i = 0; i < cs; ++i)
|
||||
if (m & rem[i])
|
||||
{
|
||||
auto r = rem[i];
|
||||
trace << "rem[" << i << "] = " << r << " requires a copy\n";
|
||||
unsigned base = res->new_states(states.size());
|
||||
for (auto s: states)
|
||||
state_map[s] = base++;
|
||||
auto k = keep[i];
|
||||
auto a = add[i];
|
||||
for (auto s: states)
|
||||
{
|
||||
auto ns = state_map[s];
|
||||
for (auto& t: aut->out(s))
|
||||
{
|
||||
if ((t.acc & r) || si.scc_of(t.dst) != n)
|
||||
continue;
|
||||
auto nd = state_map[t.dst];
|
||||
res->new_edge(ns, nd, t.cond, (t.acc & k) | a);
|
||||
// We need only one non-deterministic jump per
|
||||
// cycle. As an approximation, we only do
|
||||
// them on back-links.
|
||||
if (t.dst <= s)
|
||||
{
|
||||
acc_cond::mark_t a = 0U;
|
||||
if (sbacc)
|
||||
a = (t.acc & main_sets) | main_add;
|
||||
res->new_edge(s, nd, t.cond, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the input had no Inf, the output is a state-based automaton.
|
||||
if (allinf == 0U)
|
||||
res->prop_state_acc(true);
|
||||
|
||||
res->purge_dead_states();
|
||||
trace << "before cleanup: " << res->get_acceptance() << '\n';
|
||||
cleanup_acceptance_here(res);
|
||||
trace << "after cleanup: " << res->get_acceptance() << '\n';
|
||||
res->merge_edges();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Rewrite an automaton without Fin acceptance.
|
||||
SPOT_API twa_graph_ptr
|
||||
remove_fin(const const_twa_graph_ptr& aut);
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "remprop.hh"
|
||||
#include "twaalgos/mask.hh"
|
||||
#include "misc/casts.hh"
|
||||
#include <ctype.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static
|
||||
void unexpected_char(const char* arg, const char* pos)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "unexpected ";
|
||||
if (isprint(*pos))
|
||||
out << '\'' << *pos << '\'';
|
||||
else
|
||||
out << "character";
|
||||
out << " at position " << pos - arg << " in '";
|
||||
out << arg << '\'';
|
||||
throw std::invalid_argument(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void remove_ap::add_ap(const char* arg)
|
||||
{
|
||||
auto start = arg;
|
||||
while (*start)
|
||||
{
|
||||
while (*start == ' ' || *start == '\t')
|
||||
++start;
|
||||
if (!*start)
|
||||
break;
|
||||
if (*start == ',' || *start == '=')
|
||||
unexpected_char(arg, start);
|
||||
formula the_ap = nullptr;
|
||||
|
||||
if (*start == '"')
|
||||
{
|
||||
auto end = ++start;
|
||||
while (*end && *end != '"')
|
||||
{
|
||||
if (*end == '\\')
|
||||
++end;
|
||||
++end;
|
||||
}
|
||||
if (!*end)
|
||||
{
|
||||
std::string s = "missing closing '\"' in ";
|
||||
s += arg;
|
||||
throw std::invalid_argument(s);
|
||||
}
|
||||
std::string ap(start, end - start);
|
||||
the_ap = formula::ap(ap);
|
||||
do
|
||||
++end;
|
||||
while (*end == ' ' || *end == '\t');
|
||||
start = end;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto end = start;
|
||||
while (*end && *end != ',' && *end != '=')
|
||||
++end;
|
||||
auto rend = end;
|
||||
while (rend > start && (rend[-1] == ' ' || rend[-1] == '\t'))
|
||||
--rend;
|
||||
std::string ap(start, rend - start);
|
||||
the_ap = formula::ap(ap);
|
||||
start = end;
|
||||
}
|
||||
if (*start)
|
||||
{
|
||||
if (!(*start == ',' || *start == '='))
|
||||
unexpected_char(arg, start);
|
||||
if (*start == '=')
|
||||
{
|
||||
do
|
||||
++start;
|
||||
while (*start == ' ' || *start == '\t');
|
||||
if (*start == '0')
|
||||
props_neg.insert(the_ap);
|
||||
else if (*start == '1')
|
||||
props_pos.insert(the_ap);
|
||||
else
|
||||
unexpected_char(arg, start);
|
||||
the_ap = nullptr;
|
||||
do
|
||||
++start;
|
||||
while (*start == ' ' || *start == '\t');
|
||||
}
|
||||
if (*start)
|
||||
{
|
||||
if (*start != ',')
|
||||
unexpected_char(arg, start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
if (the_ap)
|
||||
props_exist.insert(the_ap);
|
||||
}
|
||||
}
|
||||
|
||||
twa_graph_ptr remove_ap::strip(const_twa_graph_ptr aut) const
|
||||
{
|
||||
bdd restrict = bddtrue;
|
||||
bdd exist = bddtrue;
|
||||
auto d = aut->get_dict();
|
||||
|
||||
twa_graph_ptr res = make_twa_graph(d);
|
||||
res->copy_ap_of(aut);
|
||||
res->prop_copy(aut, { true, true, false, false });
|
||||
res->copy_acceptance_of(aut);
|
||||
|
||||
for (auto ap: props_exist)
|
||||
{
|
||||
int v = d->has_registered_proposition(ap, aut);
|
||||
if (v >= 0)
|
||||
{
|
||||
exist &= bdd_ithvar(v);
|
||||
d->unregister_variable(v, res);
|
||||
}
|
||||
}
|
||||
for (auto ap: props_pos)
|
||||
{
|
||||
int v = d->has_registered_proposition(ap, aut);
|
||||
if (v >= 0)
|
||||
{
|
||||
restrict &= bdd_ithvar(v);
|
||||
d->unregister_variable(v, res);
|
||||
}
|
||||
}
|
||||
for (auto ap: props_neg)
|
||||
{
|
||||
int v = d->has_registered_proposition(ap, aut);
|
||||
if (v >= 0)
|
||||
{
|
||||
restrict &= bdd_nithvar(v);
|
||||
d->unregister_variable(v, res);
|
||||
}
|
||||
}
|
||||
|
||||
transform_accessible(aut, res, [&](unsigned, bdd& cond,
|
||||
acc_cond::mark_t&, unsigned)
|
||||
{
|
||||
cond = bdd_restrict(bdd_exist(cond, exist),
|
||||
restrict);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 <vector>
|
||||
#include "tl/formula.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
class SPOT_API remove_ap
|
||||
{
|
||||
std::set<formula> props_exist;
|
||||
std::set<formula> props_pos;
|
||||
std::set<formula> props_neg;
|
||||
public:
|
||||
void add_ap(const char* ap_csv);
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return props_exist.empty() && props_pos.empty() && props_neg.empty();
|
||||
}
|
||||
|
||||
twa_graph_ptr strip(const_twa_graph_ptr aut) const;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 <vector>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include "sbacc.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
twa_graph_ptr sbacc(twa_graph_ptr old)
|
||||
{
|
||||
if (old->prop_state_acc())
|
||||
return old;
|
||||
|
||||
auto res = make_twa_graph(old->get_dict());
|
||||
res->copy_ap_of(old);
|
||||
res->copy_acceptance_of(old);
|
||||
res->prop_copy(old, {false, true, true, true});
|
||||
res->prop_state_acc(true);
|
||||
|
||||
typedef std::pair<unsigned, acc_cond::mark_t> pair_t;
|
||||
std::map<pair_t, unsigned> s2n;
|
||||
|
||||
std::vector<std::pair<pair_t, unsigned>> todo;
|
||||
|
||||
auto new_state =
|
||||
[&](unsigned state, acc_cond::mark_t m) -> unsigned
|
||||
{
|
||||
pair_t x(state, m);
|
||||
auto p = s2n.emplace(x, 0);
|
||||
if (p.second) // This is a new state
|
||||
{
|
||||
unsigned s = res->new_state();
|
||||
p.first->second = s;
|
||||
todo.emplace_back(x, s);
|
||||
}
|
||||
return p.first->second;
|
||||
};
|
||||
|
||||
// Find any edge going into the initial state, and use its
|
||||
// acceptance as mark.
|
||||
acc_cond::mark_t init_acc = 0U;
|
||||
unsigned old_init = old->get_init_state_number();
|
||||
for (auto& t: old->edges())
|
||||
if (t.dst == old_init)
|
||||
{
|
||||
init_acc = t.acc;
|
||||
break;
|
||||
}
|
||||
|
||||
res->set_init_state(new_state(old_init, init_acc));
|
||||
while (!todo.empty())
|
||||
{
|
||||
auto one = todo.back();
|
||||
todo.pop_back();
|
||||
for (auto& t: old->out(one.first.first))
|
||||
res->new_edge(one.second,
|
||||
new_state(t.dst, t.acc),
|
||||
t.cond,
|
||||
one.first.second);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Transform an automaton to use state-based acceptance
|
||||
///
|
||||
/// This is independent on the acceptance condition used.
|
||||
SPOT_API twa_graph_ptr sbacc(twa_graph_ptr aut);
|
||||
}
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 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/>.
|
||||
|
||||
#include "sccfilter.hh"
|
||||
#include "reachiter.hh"
|
||||
#include "sccinfo.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// BDD.id -> Acc number
|
||||
typedef std::map<int, unsigned> accremap_t;
|
||||
typedef std::vector<accremap_t> remap_table_t;
|
||||
|
||||
typedef std::tuple<bool, bdd, acc_cond::mark_t> filtered_trans;
|
||||
|
||||
|
||||
// SCC filters are objects with two methods:
|
||||
// state(src) return true iff s should be kept
|
||||
// trans(src, dst, cond, acc) returns a triplet
|
||||
// (keep, cond2, acc2) where keep is a Boolean stating if the
|
||||
// edge should be kept, and cond2/acc2
|
||||
// give replacement values for cond/acc
|
||||
struct id_filter
|
||||
{
|
||||
scc_info* si;
|
||||
id_filter(scc_info* si)
|
||||
: si(si)
|
||||
{
|
||||
}
|
||||
|
||||
// Accept all states
|
||||
bool state(unsigned)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void fix_acceptance(const twa_graph_ptr& out)
|
||||
{
|
||||
out->copy_acceptance_of(this->si->get_aut());
|
||||
}
|
||||
|
||||
// Accept all edges, unmodified
|
||||
filtered_trans trans(unsigned, unsigned, bdd cond, acc_cond::mark_t acc)
|
||||
{
|
||||
return filtered_trans{true, cond, acc};
|
||||
}
|
||||
};
|
||||
|
||||
// Remove useless states.
|
||||
template <class next_filter = id_filter>
|
||||
struct state_filter: next_filter
|
||||
{
|
||||
template<typename... Args>
|
||||
state_filter(scc_info* si, Args&&... args)
|
||||
: next_filter(si, std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
bool state(unsigned s)
|
||||
{
|
||||
return this->next_filter::state(s) && this->si->is_useful_state(s);
|
||||
}
|
||||
};
|
||||
|
||||
// Suspension filter, used only by compsusp.cc
|
||||
template <class next_filter = id_filter>
|
||||
struct susp_filter: next_filter
|
||||
{
|
||||
bdd suspvars;
|
||||
bdd ignoredvars;
|
||||
bool early_susp;
|
||||
|
||||
template<typename... Args>
|
||||
susp_filter(scc_info* si,
|
||||
bdd suspvars, bdd ignoredvars, bool early_susp,
|
||||
Args&&... args)
|
||||
: next_filter(si, std::forward<Args>(args)...),
|
||||
suspvars(suspvars),
|
||||
ignoredvars(ignoredvars),
|
||||
early_susp(early_susp)
|
||||
{
|
||||
}
|
||||
|
||||
filtered_trans trans(unsigned src, unsigned dst,
|
||||
bdd cond, acc_cond::mark_t acc)
|
||||
{
|
||||
bool keep;
|
||||
std::tie(keep, cond, acc) =
|
||||
this->next_filter::trans(src, dst, cond, acc);
|
||||
|
||||
if (keep)
|
||||
{
|
||||
// Always remove ignored variables
|
||||
cond = bdd_exist(cond, ignoredvars);
|
||||
|
||||
// Remove the suspension variables only if
|
||||
// the destination in a rejecting SCC,
|
||||
// or if we are between SCC with early_susp unset.
|
||||
unsigned u = this->si->scc_of(dst);
|
||||
if (this->si->is_rejecting_scc(u)
|
||||
|| (!early_susp && (u != this->si->scc_of(src))))
|
||||
cond = bdd_exist(cond, suspvars);
|
||||
}
|
||||
|
||||
return filtered_trans(keep, cond, acc);
|
||||
}
|
||||
};
|
||||
|
||||
// Remove acceptance conditions from all edges outside of
|
||||
// non-accepting SCCs. If "RemoveAll" is false, keep those on
|
||||
// transitions entering accepting SCCs. If "PreserveSBA", is set
|
||||
// only touch a transition if all its neighbor siblings can be
|
||||
// touched as well.
|
||||
template <bool RemoveAll, bool PreserveSBA, class next_filter = id_filter>
|
||||
struct acc_filter_mask: next_filter
|
||||
{
|
||||
acc_cond::mark_t accmask;
|
||||
|
||||
template<typename... Args>
|
||||
acc_filter_mask(scc_info* si, Args&&... args)
|
||||
: next_filter(si, std::forward<Args>(args)...)
|
||||
{
|
||||
acc_cond::mark_t fin;
|
||||
acc_cond::mark_t inf;
|
||||
std::tie(inf, fin) =
|
||||
si->get_aut()->acc().get_acceptance().used_inf_fin_sets();
|
||||
// If an SCC is rejecting, we can mask all the sets that are
|
||||
// used only as Inf in the acceptance.
|
||||
accmask = ~(inf - fin);
|
||||
}
|
||||
|
||||
filtered_trans trans(unsigned src, unsigned dst,
|
||||
bdd cond, acc_cond::mark_t acc)
|
||||
{
|
||||
bool keep;
|
||||
std::tie(keep, cond, acc) =
|
||||
this->next_filter::trans(src, dst, cond, acc);
|
||||
|
||||
if (keep)
|
||||
{
|
||||
unsigned u = this->si->scc_of(src);
|
||||
unsigned v = this->si->scc_of(dst);
|
||||
// The basic rules are as follows:
|
||||
//
|
||||
// - If an edge is between two SCCs, is OK to remove
|
||||
// all acceptance sets, as this edge cannot be part
|
||||
// of any loop.
|
||||
// - If an edge is in an non-accepting SCC, we can only
|
||||
// remove the Inf sets, as removinf the Fin sets
|
||||
// might make the SCC accepting.
|
||||
//
|
||||
// The above rules are made more complex with two flags:
|
||||
//
|
||||
// - If PreserveSBA is set, we have to tree a transition
|
||||
// leaving an SCC as other transitions inside the SCC,
|
||||
// otherwise we will break the property that all
|
||||
// transitions leaving the same state have identical set
|
||||
// membership.
|
||||
// - If RemoveAll is false, we like to keep the membership
|
||||
// of transitions entering an SCC. This can only be
|
||||
// done if PreserveSBA is unset, unfortunately.
|
||||
if (u == v)
|
||||
{
|
||||
if (this->si->is_rejecting_scc(u))
|
||||
acc &= accmask;
|
||||
}
|
||||
else if (PreserveSBA && this->si->is_rejecting_scc(u))
|
||||
{
|
||||
if (!this->si->is_trivial(u))
|
||||
acc &= accmask; // No choice.
|
||||
else if (RemoveAll)
|
||||
acc = 0U;
|
||||
}
|
||||
else if (!PreserveSBA)
|
||||
{
|
||||
if (RemoveAll)
|
||||
acc = 0U;
|
||||
else if (this->si->is_rejecting_scc(v))
|
||||
acc &= accmask;
|
||||
}
|
||||
}
|
||||
return filtered_trans(keep, cond, acc);
|
||||
}
|
||||
};
|
||||
|
||||
// Simplify redundant acceptance sets used in each SCCs.
|
||||
template <class next_filter = id_filter>
|
||||
struct acc_filter_simplify: next_filter
|
||||
{
|
||||
// Acceptance sets to strip in each SCC.
|
||||
std::vector<acc_cond::mark_t> strip_;
|
||||
|
||||
template<typename... Args>
|
||||
acc_filter_simplify(scc_info* si, Args&&... args)
|
||||
: next_filter(si, std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
void fix_acceptance(const twa_graph_ptr& out)
|
||||
{
|
||||
auto& acc = this->si->get_aut()->acc();
|
||||
if (!acc.is_generalized_buchi())
|
||||
throw std::runtime_error
|
||||
("simplification of SCC acceptance works only with "
|
||||
"generalized Büchi acceptance");
|
||||
|
||||
unsigned scc_count = this->si->scc_count();
|
||||
auto used_acc = this->si->used_acc();
|
||||
assert(used_acc.size() == scc_count);
|
||||
strip_.resize(scc_count);
|
||||
std::vector<unsigned> cnt(scc_count); // # of useful sets in each SCC
|
||||
unsigned max = 0; // Max number of useful sets
|
||||
for (unsigned n = 0; n < scc_count; ++n)
|
||||
{
|
||||
if (this->si->is_rejecting_scc(n))
|
||||
continue;
|
||||
strip_[n] = acc.useless(used_acc[n].begin(), used_acc[n].end());
|
||||
cnt[n] = acc.num_sets() - strip_[n].count();
|
||||
if (cnt[n] > max)
|
||||
max = cnt[n];
|
||||
}
|
||||
// Now that we know about the max number of acceptance
|
||||
// conditions, add extra acceptance conditions to those SCC
|
||||
// that do not have enough.
|
||||
for (unsigned n = 0; n < scc_count; ++n)
|
||||
{
|
||||
if (this->si->is_rejecting_scc(n))
|
||||
continue;
|
||||
if (cnt[n] < max)
|
||||
strip_[n].remove_some(max - cnt[n]);
|
||||
}
|
||||
|
||||
out->set_generalized_buchi(max);
|
||||
}
|
||||
|
||||
filtered_trans trans(unsigned src, unsigned dst, bdd cond,
|
||||
acc_cond::mark_t acc)
|
||||
{
|
||||
bool keep;
|
||||
std::tie(keep, cond, acc) =
|
||||
this->next_filter::trans(src, dst, cond, acc);
|
||||
|
||||
if (keep && acc)
|
||||
{
|
||||
unsigned u = this->si->scc_of(dst);
|
||||
|
||||
if (this->si->is_rejecting_scc(u))
|
||||
acc = 0U;
|
||||
else
|
||||
acc = acc.strip(strip_[u]);
|
||||
}
|
||||
return filtered_trans{keep, cond, acc};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<class F, typename... Args>
|
||||
twa_graph_ptr scc_filter_apply(const_twa_graph_ptr aut,
|
||||
scc_info* given_si, Args&&... args)
|
||||
{
|
||||
unsigned in_n = aut->num_states();
|
||||
if (in_n == 0) // nothing to filter.
|
||||
return make_twa_graph(aut, twa::prop_set::all());
|
||||
|
||||
twa_graph_ptr filtered = make_twa_graph(aut->get_dict());
|
||||
filtered->copy_ap_of(aut);
|
||||
|
||||
// Compute scc_info if not supplied.
|
||||
scc_info* si = given_si;
|
||||
if (!si)
|
||||
si = new scc_info(aut);
|
||||
si->determine_unknown_acceptance();
|
||||
|
||||
F filter(si, std::forward<Args>(args)...);
|
||||
|
||||
// Renumber all useful states.
|
||||
unsigned out_n = 0; // Number of output states.
|
||||
std::vector<unsigned> inout; // Associate old states to new ones.
|
||||
inout.reserve(in_n);
|
||||
for (unsigned i = 0; i < in_n; ++i)
|
||||
if (filter.state(i))
|
||||
inout.push_back(out_n++);
|
||||
else
|
||||
inout.push_back(-1U);
|
||||
|
||||
filter.fix_acceptance(filtered);
|
||||
filtered->new_states(out_n);
|
||||
for (unsigned isrc = 0; isrc < in_n; ++isrc)
|
||||
{
|
||||
unsigned osrc = inout[isrc];
|
||||
if (osrc >= out_n)
|
||||
continue;
|
||||
for (auto& t: aut->out(isrc))
|
||||
{
|
||||
unsigned odst = inout[t.dst];
|
||||
if (odst >= out_n)
|
||||
continue;
|
||||
bool want;
|
||||
bdd cond;
|
||||
acc_cond::mark_t acc;
|
||||
std::tie(want, cond, acc) =
|
||||
filter.trans(isrc, t.dst, t.cond, t.acc);
|
||||
if (want)
|
||||
filtered->new_edge(osrc, odst, cond, acc);
|
||||
}
|
||||
}
|
||||
if (!given_si)
|
||||
delete si;
|
||||
// If the initial state has been filtered out, we have to create
|
||||
// a new one (not doing so may cause empty automata, which in turn
|
||||
// cause all sort of issue with algorithms assuming an automaton
|
||||
// has one initial state).
|
||||
auto init = inout[aut->get_init_state_number()];
|
||||
filtered->set_init_state(init < out_n ? init : filtered->new_state());
|
||||
return filtered;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
scc_filter_states(const const_twa_graph_ptr& aut, bool remove_all_useless,
|
||||
scc_info* given_si)
|
||||
{
|
||||
twa_graph_ptr res;
|
||||
if (remove_all_useless)
|
||||
res = scc_filter_apply<state_filter
|
||||
<acc_filter_mask<true, true>>>(aut, given_si);
|
||||
else
|
||||
res = scc_filter_apply<state_filter
|
||||
<acc_filter_mask<false, true>>>(aut, given_si);
|
||||
res->prop_copy(aut, { true, true, true, true });
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
scc_filter(const const_twa_graph_ptr& aut, bool remove_all_useless,
|
||||
scc_info* given_si)
|
||||
{
|
||||
twa_graph_ptr res;
|
||||
// acc_filter_simplify only work for generalized Büchi
|
||||
if (aut->acc().is_generalized_buchi())
|
||||
{
|
||||
if (remove_all_useless)
|
||||
res =
|
||||
scc_filter_apply<state_filter
|
||||
<acc_filter_mask
|
||||
<true, false,
|
||||
acc_filter_simplify<>>>>(aut, given_si);
|
||||
else
|
||||
res =
|
||||
scc_filter_apply<state_filter
|
||||
<acc_filter_mask
|
||||
<false, false,
|
||||
acc_filter_simplify<>>>>(aut, given_si);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (remove_all_useless)
|
||||
res = scc_filter_apply<state_filter
|
||||
<acc_filter_mask
|
||||
<true, false>>>(aut, given_si);
|
||||
else
|
||||
res = scc_filter_apply<state_filter
|
||||
<acc_filter_mask
|
||||
<false, false>>>(aut, given_si);
|
||||
}
|
||||
res->merge_edges();
|
||||
res->prop_copy(aut,
|
||||
{ false, // state-based acceptance is not preserved
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
scc_filter_susp(const const_twa_graph_ptr& aut, bool remove_all_useless,
|
||||
bdd suspvars, bdd ignoredvars, bool early_susp,
|
||||
scc_info* given_si)
|
||||
{
|
||||
twa_graph_ptr res;
|
||||
if (remove_all_useless)
|
||||
res = scc_filter_apply<susp_filter
|
||||
<state_filter
|
||||
<acc_filter_mask
|
||||
<true, false,
|
||||
acc_filter_simplify<>>>>>(aut, given_si,
|
||||
suspvars,
|
||||
ignoredvars,
|
||||
early_susp);
|
||||
else
|
||||
res = scc_filter_apply<susp_filter
|
||||
<state_filter
|
||||
<acc_filter_mask
|
||||
<false, false,
|
||||
acc_filter_simplify<>>>>>(aut, given_si,
|
||||
suspvars,
|
||||
ignoredvars,
|
||||
early_susp);
|
||||
res->merge_edges();
|
||||
res->prop_copy(aut,
|
||||
{ false, // state-based acceptance is not preserved
|
||||
true,
|
||||
false, // determinism may not be preserved
|
||||
false, // stutter inv. of suspvars probably altered
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2009, 2010, 2012, 2013, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "misc/common.hh"
|
||||
#include <bddx.h>
|
||||
#include "twa/fwd.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
class scc_info;
|
||||
|
||||
|
||||
/// \brief Prune unaccepting SCCs and remove superfluous acceptance
|
||||
/// conditions.
|
||||
///
|
||||
/// This function will explore the SCCs of the automaton and remove
|
||||
/// dead SCCs (i.e. SCC that are not accepting, and those with no
|
||||
/// exit path leading to an accepting SCC).
|
||||
///
|
||||
/// Additionally, for Generalized Büchi acceptance, this will try to
|
||||
/// remove useless acceptance conditions. This operation may
|
||||
/// diminish the number of acceptance condition of the automaton
|
||||
/// (for instance when two acceptance conditions are always used
|
||||
/// together we only keep one) but it will never remove all
|
||||
/// acceptance conditions, even if it would be OK to have zero.
|
||||
///
|
||||
/// Acceptance conditions on transitions going to rejecting SCCs are
|
||||
/// all removed. Acceptance conditions going to an accepting SCC
|
||||
/// and coming from another SCC are only removed if \a
|
||||
/// remove_all_useless is set. The default value of \a
|
||||
/// remove_all_useless is \c false because some algorithms (like the
|
||||
/// degeneralization) will work better if transitions going to an
|
||||
/// accepting SCC are accepting.
|
||||
///
|
||||
/// If \a given_sm is supplied, the function will use its result
|
||||
/// without computing a map of its own.
|
||||
///
|
||||
/// \warning Calling scc_filter on a TωA that has the SBA property
|
||||
/// (i.e., transitions leaving accepting states are all marked as
|
||||
/// accepting) may destroy this property. Use scc_filter_states()
|
||||
/// instead.
|
||||
SPOT_API twa_graph_ptr
|
||||
scc_filter(const const_twa_graph_ptr& aut, bool remove_all_useless = false,
|
||||
scc_info* given_si = nullptr);
|
||||
|
||||
/// \brief Prune unaccepting SCCs.
|
||||
///
|
||||
/// This is an abridged version of scc_filter(), that preserves
|
||||
/// state-based acceptance. I.e., if the input TωA has the SBA
|
||||
/// property, (i.e., transitions leaving accepting states are all
|
||||
/// marked as accepting), then the output TωA will also have that
|
||||
/// property.
|
||||
SPOT_API twa_graph_ptr
|
||||
scc_filter_states(const const_twa_graph_ptr& aut,
|
||||
bool remove_all_useless = false,
|
||||
scc_info* given_si = nullptr);
|
||||
|
||||
/// \brief Prune unaccepting SCCs, superfluous acceptance
|
||||
/// sets, and suspension variables.
|
||||
///
|
||||
/// In addition to removing useless states, and acceptance sets,
|
||||
/// this remove all ignoredvars occurring in conditions, and all
|
||||
/// suspvars in conditions leadings to non-accepting SCC (as well
|
||||
/// as the conditions between two SCCs if early_susp is false).
|
||||
///
|
||||
/// This is used by compsusp(), and is probably useless for any
|
||||
/// other use.
|
||||
SPOT_API twa_graph_ptr
|
||||
scc_filter_susp(const const_twa_graph_ptr& aut, bool remove_all_useless,
|
||||
bdd suspvars, bdd ignoredvars, bool early_susp,
|
||||
scc_info* given_si = nullptr);
|
||||
}
|
||||
|
|
@ -1,375 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2014, 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 "sccinfo.hh"
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include "twa/bddprint.hh"
|
||||
#include "twaalgos/mask.hh"
|
||||
#include "misc/escape.hh"
|
||||
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
struct scc
|
||||
{
|
||||
public:
|
||||
scc(int index, acc_cond::mark_t in_acc):
|
||||
in_acc(in_acc), index(index)
|
||||
{
|
||||
}
|
||||
|
||||
acc_cond::mark_t in_acc; // Acceptance sets on the incoming transition
|
||||
acc_cond::mark_t acc = 0U; // union of all acceptance sets in the SCC
|
||||
int index; // Index of the SCC
|
||||
bool trivial = true; // Whether the SCC has no cycle
|
||||
bool accepting = false; // Necessarily accepting
|
||||
};
|
||||
}
|
||||
|
||||
scc_info::scc_info(const_twa_graph_ptr aut)
|
||||
: aut_(aut)
|
||||
{
|
||||
unsigned n = aut->num_states();
|
||||
sccof_.resize(n, -1U);
|
||||
|
||||
std::deque<unsigned> live;
|
||||
std::deque<scc> root_; // Stack of SCC roots.
|
||||
std::vector<int> h_(n, 0);
|
||||
// Map of visited states. Values > 0 designate maximal SCC.
|
||||
// Values < 0 number states that are part of incomplete SCCs being
|
||||
// completed. 0 denotes non-visited states.
|
||||
|
||||
int num_; // Number of visited nodes, negated.
|
||||
|
||||
typedef twa_graph::graph_t::const_iterator iterator;
|
||||
typedef std::pair<unsigned, iterator> pair_state_iter;
|
||||
std::stack<pair_state_iter> todo_; // DFS stack. Holds (STATE,
|
||||
// ITERATOR) pairs where
|
||||
// ITERATOR is an iterator over
|
||||
// the successors of STATE.
|
||||
// ITERATOR should always be
|
||||
// freed when TODO is popped,
|
||||
// but STATE should not because
|
||||
// it is used as a key in H.
|
||||
|
||||
|
||||
// Setup depth-first search from the initial state.
|
||||
if (n > 0)
|
||||
{
|
||||
unsigned init = aut->get_init_state_number();
|
||||
num_ = -1;
|
||||
h_[init] = num_;
|
||||
root_.emplace_back(num_, 0U);
|
||||
todo_.emplace(init, aut->out(init).begin());
|
||||
live.emplace_back(init);
|
||||
}
|
||||
|
||||
while (!todo_.empty())
|
||||
{
|
||||
// We are looking at the next successor in SUCC.
|
||||
iterator succ = todo_.top().second;
|
||||
|
||||
// If there is no more successor, backtrack.
|
||||
if (!succ)
|
||||
{
|
||||
// We have explored all successors of state CURR.
|
||||
unsigned curr = todo_.top().first;
|
||||
|
||||
// Backtrack TODO_.
|
||||
todo_.pop();
|
||||
|
||||
// When backtracking the root of an SCC, we must also
|
||||
// remove that SCC from the ARC/ROOT stacks. We must
|
||||
// discard from H all reachable states from this SCC.
|
||||
assert(!root_.empty());
|
||||
if (root_.back().index == h_[curr])
|
||||
{
|
||||
unsigned num = node_.size();
|
||||
auto acc = root_.back().acc;
|
||||
bool triv = root_.back().trivial;
|
||||
node_.emplace_back(acc, triv);
|
||||
|
||||
// Move all elements of this SCC from the live stack
|
||||
// to the the node.
|
||||
auto i = std::find(live.rbegin(), live.rend(), curr);
|
||||
assert(i != live.rend());
|
||||
++i; // Because base() does -1
|
||||
auto& nbs = node_.back().states_;
|
||||
nbs.insert(nbs.end(), i.base(), live.end());
|
||||
live.erase(i.base(), live.end());
|
||||
|
||||
std::set<unsigned> dests;
|
||||
unsigned np1 = num + 1;
|
||||
for (unsigned s: nbs)
|
||||
{
|
||||
sccof_[s] = num;
|
||||
h_[s] = np1;
|
||||
}
|
||||
// Gather all successor SCCs
|
||||
for (unsigned s: nbs)
|
||||
for (auto& t: aut->out(s))
|
||||
{
|
||||
unsigned n = sccof_[t.dst];
|
||||
assert(n != -1U);
|
||||
if (n == num)
|
||||
continue;
|
||||
dests.insert(n);
|
||||
}
|
||||
auto& succ = node_.back().succ_;
|
||||
succ.insert(succ.end(), dests.begin(), dests.end());
|
||||
node_.back().accepting_ =
|
||||
!triv && root_.back().accepting;
|
||||
node_.back().rejecting_ =
|
||||
triv || !aut->acc().inf_satisfiable(acc);
|
||||
root_.pop_back();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a successor to look at.
|
||||
// Fetch the values we are interested in...
|
||||
unsigned dest = succ->dst;
|
||||
auto acc = succ->acc;
|
||||
++todo_.top().second;
|
||||
|
||||
// We do not need SUCC from now on.
|
||||
|
||||
// Are we going to a new state?
|
||||
int spi = h_[dest];
|
||||
if (spi == 0)
|
||||
{
|
||||
// Yes. Number it, stack it, and register its successors
|
||||
// for later processing.
|
||||
h_[dest] = --num_;
|
||||
root_.emplace_back(num_, acc);
|
||||
todo_.emplace(dest, aut->out(dest).begin());
|
||||
live.emplace_back(dest);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We already know the state.
|
||||
|
||||
// Have we reached a maximal SCC?
|
||||
if (spi > 0)
|
||||
continue;
|
||||
|
||||
// Now this is the most interesting case. We have reached a
|
||||
// state S1 which is already part of a non-dead SCC. Any such
|
||||
// non-dead SCC has necessarily been crossed by our path to
|
||||
// this state: there is a state S2 in our path which belongs
|
||||
// to this SCC too. We are going to merge all states between
|
||||
// this S1 and S2 into this SCC..
|
||||
//
|
||||
// This merge is easy to do because the order of the SCC in
|
||||
// ROOT is descending: we just have to merge all SCCs from the
|
||||
// top of ROOT that have an index lesser than the one of
|
||||
// the SCC of S2 (called the "threshold").
|
||||
int threshold = spi;
|
||||
bool is_accepting = false;
|
||||
// If this is a self-loop, check its acceptance alone.
|
||||
if (dest == succ->src)
|
||||
is_accepting = aut->acc().accepting(acc);
|
||||
|
||||
assert(!root_.empty());
|
||||
while (threshold > root_.back().index)
|
||||
{
|
||||
acc |= root_.back().acc;
|
||||
acc |= root_.back().in_acc;
|
||||
is_accepting |= root_.back().accepting;
|
||||
root_.pop_back();
|
||||
assert(!root_.empty());
|
||||
}
|
||||
|
||||
// Note that we do not always have
|
||||
// threshold == root_.back().index
|
||||
// after this loop, the SCC whose index is threshold might have
|
||||
// been merged with a higher SCC.
|
||||
|
||||
// Accumulate all acceptance conditions, states, SCC
|
||||
// successors, and conditions into the merged SCC.
|
||||
root_.back().acc |= acc;
|
||||
root_.back().accepting |= is_accepting
|
||||
|| aut->acc().accepting(root_.back().acc);
|
||||
// This SCC is no longer trivial.
|
||||
root_.back().trivial = false;
|
||||
}
|
||||
|
||||
determine_usefulness();
|
||||
}
|
||||
|
||||
void scc_info::determine_usefulness()
|
||||
{
|
||||
// An SCC is useful if it is not rejecting or it has a successor
|
||||
// SCC that is useful.
|
||||
unsigned scccount = scc_count();
|
||||
for (unsigned i = 0; i < scccount; ++i)
|
||||
{
|
||||
if (!node_[i].is_rejecting())
|
||||
{
|
||||
node_[i].useful_ = true;
|
||||
continue;
|
||||
}
|
||||
node_[i].useful_ = false;
|
||||
for (unsigned j: node_[i].succ())
|
||||
if (node_[j].is_useful())
|
||||
{
|
||||
node_[i].useful_ = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::set<acc_cond::mark_t> scc_info::used_acc_of(unsigned scc) const
|
||||
{
|
||||
std::set<acc_cond::mark_t> res;
|
||||
for (auto src: states_of(scc))
|
||||
for (auto& t: aut_->out(src))
|
||||
if (scc_of(t.dst) == scc)
|
||||
res.insert(t.acc);
|
||||
return res;
|
||||
}
|
||||
|
||||
acc_cond::mark_t scc_info::acc_sets_of(unsigned scc) const
|
||||
{
|
||||
acc_cond::mark_t res = 0U;
|
||||
for (auto src: states_of(scc))
|
||||
for (auto& t: aut_->out(src))
|
||||
if (scc_of(t.dst) == scc)
|
||||
res |= t.acc;
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::set<acc_cond::mark_t>> scc_info::used_acc() const
|
||||
{
|
||||
unsigned n = aut_->num_states();
|
||||
std::vector<std::set<acc_cond::mark_t>> result(scc_count());
|
||||
|
||||
for (unsigned src = 0; src < n; ++src)
|
||||
{
|
||||
unsigned src_scc = scc_of(src);
|
||||
if (src_scc == -1U || is_rejecting_scc(src_scc))
|
||||
continue;
|
||||
auto& s = result[src_scc];
|
||||
for (auto& t: aut_->out(src))
|
||||
{
|
||||
if (scc_of(t.dst) != src_scc)
|
||||
continue;
|
||||
s.insert(t.acc);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<bool> scc_info::weak_sccs() const
|
||||
{
|
||||
unsigned n = scc_count();
|
||||
std::vector<bool> result(scc_count());
|
||||
auto acc = used_acc();
|
||||
for (unsigned s = 0; s < n; ++s)
|
||||
result[s] = is_rejecting_scc(s) || acc[s].size() == 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
bdd scc_info::scc_ap_support(unsigned scc) const
|
||||
{
|
||||
bdd support = bddtrue;
|
||||
for (auto s: states_of(scc))
|
||||
for (auto& t: aut_->out(s))
|
||||
support &= bdd_support(t.cond);
|
||||
return support;
|
||||
}
|
||||
|
||||
void scc_info::determine_unknown_acceptance()
|
||||
{
|
||||
std::vector<bool> k;
|
||||
unsigned n = scc_count();
|
||||
bool changed = false;
|
||||
for (unsigned s = 0; s < n; ++s)
|
||||
if (!is_rejecting_scc(s) && !is_accepting_scc(s))
|
||||
{
|
||||
auto& node = node_[s];
|
||||
if (k.empty())
|
||||
k.resize(aut_->num_states());
|
||||
for (auto i: node.states_)
|
||||
k[i] = true;
|
||||
if (mask_keep_states(aut_, k, node.states_.front())->is_empty())
|
||||
node.rejecting_ = true;
|
||||
else
|
||||
node.accepting_ = true;
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
determine_usefulness();
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
dump_scc_info_dot(std::ostream& out,
|
||||
const_twa_graph_ptr aut, scc_info* sccinfo)
|
||||
{
|
||||
scc_info* m = sccinfo ? sccinfo : new scc_info(aut);
|
||||
|
||||
out << "digraph G {\n i [label=\"\", style=invis, height=0]\n";
|
||||
int start = m->scc_of(aut->get_init_state_number());
|
||||
out << " i -> " << start << std::endl;
|
||||
|
||||
std::vector<bool> seen(m->scc_count());
|
||||
seen[start] = true;
|
||||
|
||||
std::queue<int> q;
|
||||
q.push(start);
|
||||
while (!q.empty())
|
||||
{
|
||||
int state = q.front();
|
||||
q.pop();
|
||||
|
||||
out << " " << state << " [shape=box,"
|
||||
<< (aut->acc().accepting(m->acc(state)) ? "style=bold," : "")
|
||||
<< "label=\"" << state;
|
||||
{
|
||||
size_t n = m->states_of(state).size();
|
||||
out << " (" << n << " state";
|
||||
if (n > 1)
|
||||
out << 's';
|
||||
out << ')';
|
||||
}
|
||||
out << "\"]\n";
|
||||
|
||||
for (unsigned dest: m->succ(state))
|
||||
{
|
||||
out << " " << state << " -> " << dest << '\n';
|
||||
if (seen[dest])
|
||||
continue;
|
||||
seen[dest] = true;
|
||||
q.push(dest);
|
||||
}
|
||||
}
|
||||
|
||||
out << "}\n";
|
||||
if (!sccinfo)
|
||||
delete m;
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2014, 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 <vector>
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
class SPOT_API scc_info
|
||||
{
|
||||
public:
|
||||
typedef std::vector<unsigned> scc_succs;
|
||||
|
||||
class scc_node
|
||||
{
|
||||
friend class scc_info;
|
||||
protected:
|
||||
scc_succs succ_;
|
||||
acc_cond::mark_t acc_;
|
||||
std::vector<unsigned> states_; // States of the component
|
||||
bool trivial_:1;
|
||||
bool accepting_:1; // Necessarily accepting
|
||||
bool rejecting_:1; // Necessarily rejecting
|
||||
bool useful_:1;
|
||||
public:
|
||||
scc_node():
|
||||
acc_(0U), trivial_(true), accepting_(false),
|
||||
rejecting_(false), useful_(false)
|
||||
{
|
||||
}
|
||||
|
||||
scc_node(acc_cond::mark_t acc, bool trivial):
|
||||
acc_(acc), trivial_(trivial), accepting_(false),
|
||||
rejecting_(false), useful_(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_trivial() const
|
||||
{
|
||||
return trivial_;
|
||||
}
|
||||
|
||||
/// \brief True if we are sure that the SCC is accepting
|
||||
///
|
||||
/// Note that both is_accepting() and is_rejecting() may return
|
||||
/// false if an SCC interesects a mix of Fin and Inf sets.
|
||||
bool is_accepting() const
|
||||
{
|
||||
return accepting_;
|
||||
}
|
||||
|
||||
// True if we are sure that the SCC is rejecting
|
||||
///
|
||||
/// Note that both is_accepting() and is_rejecting() may return
|
||||
/// false if an SCC interesects a mix of Fin and Inf sets.
|
||||
bool is_rejecting() const
|
||||
{
|
||||
return rejecting_;
|
||||
}
|
||||
|
||||
bool is_useful() const
|
||||
{
|
||||
return useful_;
|
||||
}
|
||||
|
||||
acc_cond::mark_t acc_marks() const
|
||||
{
|
||||
return acc_;
|
||||
}
|
||||
|
||||
const std::vector<unsigned>& states() const
|
||||
{
|
||||
return states_;
|
||||
}
|
||||
|
||||
const scc_succs& succ() const
|
||||
{
|
||||
return succ_;
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
std::vector<unsigned> sccof_;
|
||||
std::vector<scc_node> node_;
|
||||
const_twa_graph_ptr aut_;
|
||||
|
||||
// Update the useful_ bits. Called automatically.
|
||||
void determine_usefulness();
|
||||
|
||||
const scc_node& node(unsigned scc) const
|
||||
{
|
||||
assert(scc < node_.size());
|
||||
return node_[scc];
|
||||
}
|
||||
|
||||
public:
|
||||
scc_info(const_twa_graph_ptr aut);
|
||||
|
||||
const_twa_graph_ptr get_aut() const
|
||||
{
|
||||
return aut_;
|
||||
}
|
||||
|
||||
unsigned scc_count() const
|
||||
{
|
||||
return node_.size();
|
||||
}
|
||||
|
||||
bool reachable_state(unsigned st) const
|
||||
{
|
||||
return scc_of(st) != -1U;
|
||||
}
|
||||
|
||||
unsigned scc_of(unsigned st) const
|
||||
{
|
||||
assert(st < sccof_.size());
|
||||
return sccof_[st];
|
||||
}
|
||||
|
||||
auto begin() const
|
||||
SPOT_RETURN(node_.begin());
|
||||
auto end() const
|
||||
SPOT_RETURN(node_.end());
|
||||
auto cbegin() const
|
||||
SPOT_RETURN(node_.cbegin());
|
||||
auto cend() const
|
||||
SPOT_RETURN(node_.cend());
|
||||
auto rbegin() const
|
||||
SPOT_RETURN(node_.rbegin());
|
||||
auto rend() const
|
||||
SPOT_RETURN(node_.rend());
|
||||
|
||||
const std::vector<unsigned>& states_of(unsigned scc) const
|
||||
{
|
||||
return node(scc).states();
|
||||
}
|
||||
|
||||
unsigned one_state_of(unsigned scc) const
|
||||
{
|
||||
return states_of(scc).front();
|
||||
}
|
||||
|
||||
/// \brief Get number of the SCC containing the initial state.
|
||||
unsigned initial() const
|
||||
{
|
||||
assert(scc_count() - 1 == scc_of(aut_->get_init_state_number()));
|
||||
return scc_count() - 1;
|
||||
}
|
||||
|
||||
const scc_succs& succ(unsigned scc) const
|
||||
{
|
||||
return node(scc).succ();
|
||||
}
|
||||
|
||||
bool is_trivial(unsigned scc) const
|
||||
{
|
||||
return node(scc).is_trivial();
|
||||
}
|
||||
|
||||
acc_cond::mark_t acc(unsigned scc) const
|
||||
{
|
||||
return node(scc).acc_marks();
|
||||
}
|
||||
|
||||
bool is_accepting_scc(unsigned scc) const
|
||||
{
|
||||
return node(scc).is_accepting();
|
||||
}
|
||||
|
||||
bool is_rejecting_scc(unsigned scc) const
|
||||
{
|
||||
return node(scc).is_rejecting();
|
||||
}
|
||||
|
||||
// Study the SCC that are currently reported neither as accepting
|
||||
// nor rejecting because of the presence of Fin sets
|
||||
void determine_unknown_acceptance();
|
||||
|
||||
bool is_useful_scc(unsigned scc) const
|
||||
{
|
||||
return node(scc).is_useful();
|
||||
}
|
||||
|
||||
bool is_useful_state(unsigned st) const
|
||||
{
|
||||
return reachable_state(st) && node(scc_of(st)).is_useful();
|
||||
}
|
||||
|
||||
/// \brief Return the set of all used acceptance combinations, for
|
||||
/// each accepting SCC.
|
||||
std::vector<std::set<acc_cond::mark_t>> used_acc() const;
|
||||
|
||||
std::set<acc_cond::mark_t> used_acc_of(unsigned scc) const;
|
||||
|
||||
acc_cond::mark_t acc_sets_of(unsigned scc) const;
|
||||
|
||||
std::vector<bool> weak_sccs() const;
|
||||
|
||||
bdd scc_ap_support(unsigned scc) const;
|
||||
};
|
||||
|
||||
|
||||
/// \brief Dump the SCC graph of \a aut on \a out.
|
||||
///
|
||||
/// If \a sccinfo is not given, it will be computed.
|
||||
SPOT_API std::ostream&
|
||||
dump_scc_info_dot(std::ostream& out,
|
||||
const_twa_graph_ptr aut, scc_info* sccinfo = nullptr);
|
||||
|
||||
}
|
||||
|
|
@ -1,700 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2011, 2013, 2014, 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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/>.
|
||||
|
||||
//#define TRACE
|
||||
|
||||
#include <iostream>
|
||||
#ifdef TRACE
|
||||
#define trace std::cerr
|
||||
#else
|
||||
#define trace while (0) std::cerr
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
#include "misc/hash.hh"
|
||||
#include "twa/twa.hh"
|
||||
#include "emptiness.hh"
|
||||
#include "emptiness_stats.hh"
|
||||
#include "se05.hh"
|
||||
#include "ndfs_result.hxx"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
enum color {WHITE, CYAN, BLUE, RED};
|
||||
|
||||
/// \brief Emptiness checker on spot::tgba automata having at most one
|
||||
/// acceptance condition (i.e. a TBA).
|
||||
template <typename heap>
|
||||
class se05_search : public emptiness_check, public ec_statistics
|
||||
{
|
||||
public:
|
||||
/// \brief Initialize the Magic Search algorithm on the automaton \a a
|
||||
///
|
||||
/// \pre The automaton \a a must have at most one acceptance
|
||||
/// condition (i.e. it is a TBA).
|
||||
se05_search(const const_twa_ptr a, size_t size,
|
||||
option_map o = option_map())
|
||||
: emptiness_check(a, o),
|
||||
h(size)
|
||||
{
|
||||
assert(a->num_sets() <= 1);
|
||||
}
|
||||
|
||||
virtual ~se05_search()
|
||||
{
|
||||
// Release all iterators on the stacks.
|
||||
while (!st_blue.empty())
|
||||
{
|
||||
h.pop_notify(st_blue.front().s);
|
||||
a_->release_iter(st_blue.front().it);
|
||||
st_blue.pop_front();
|
||||
}
|
||||
while (!st_red.empty())
|
||||
{
|
||||
h.pop_notify(st_red.front().s);
|
||||
a_->release_iter(st_red.front().it);
|
||||
st_red.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Perform a Magic Search.
|
||||
///
|
||||
/// \return non null pointer iff the algorithm has found a
|
||||
/// new accepting path.
|
||||
///
|
||||
/// check() can be called several times (until it returns a null
|
||||
/// pointer) to enumerate all the visited accepting paths. The method
|
||||
/// visits only a finite set of accepting paths.
|
||||
virtual emptiness_check_result_ptr check()
|
||||
{
|
||||
auto t = std::static_pointer_cast<se05_search>
|
||||
(this->emptiness_check::shared_from_this());
|
||||
if (st_red.empty())
|
||||
{
|
||||
assert(st_blue.empty());
|
||||
const state* s0 = a_->get_init_state();
|
||||
inc_states();
|
||||
h.add_new_state(s0, CYAN);
|
||||
push(st_blue, s0, bddfalse, 0U);
|
||||
if (dfs_blue())
|
||||
return std::make_shared<se05_result>(t, options());
|
||||
}
|
||||
else
|
||||
{
|
||||
h.pop_notify(st_red.front().s);
|
||||
pop(st_red);
|
||||
if (!st_red.empty() && dfs_red())
|
||||
return std::make_shared<se05_result>(t, options());
|
||||
else
|
||||
if (dfs_blue())
|
||||
return std::make_shared<se05_result>(t, options());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual std::ostream& print_stats(std::ostream &os) const
|
||||
{
|
||||
os << states() << " distinct nodes visited" << std::endl;
|
||||
os << transitions() << " transitions explored" << std::endl;
|
||||
os << max_depth() << " nodes for the maximal stack depth" << std::endl;
|
||||
if (!st_red.empty())
|
||||
{
|
||||
assert(!st_blue.empty());
|
||||
os << st_blue.size() + st_red.size() - 1
|
||||
<< " nodes for the counter example" << std::endl;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
virtual bool safe() const
|
||||
{
|
||||
return heap::Safe;
|
||||
}
|
||||
|
||||
const heap& get_heap() const
|
||||
{
|
||||
return h;
|
||||
}
|
||||
|
||||
const stack_type& get_st_blue() const
|
||||
{
|
||||
return st_blue;
|
||||
}
|
||||
|
||||
const stack_type& get_st_red() const
|
||||
{
|
||||
return st_red;
|
||||
}
|
||||
private:
|
||||
|
||||
void push(stack_type& st, const state* s,
|
||||
const bdd& label, acc_cond::mark_t acc)
|
||||
{
|
||||
inc_depth();
|
||||
twa_succ_iterator* i = a_->succ_iter(s);
|
||||
i->first();
|
||||
st.emplace_front(s, i, label, acc);
|
||||
}
|
||||
|
||||
void pop(stack_type& st)
|
||||
{
|
||||
dec_depth();
|
||||
a_->release_iter(st.front().it);
|
||||
st.pop_front();
|
||||
}
|
||||
|
||||
/// \brief Stack of the blue dfs.
|
||||
stack_type st_blue;
|
||||
|
||||
/// \brief Stack of the red dfs.
|
||||
stack_type st_red;
|
||||
|
||||
/// \brief Map where each visited state is colored
|
||||
/// by the last dfs visiting it.
|
||||
heap h;
|
||||
|
||||
bool dfs_blue()
|
||||
{
|
||||
while (!st_blue.empty())
|
||||
{
|
||||
stack_item& f = st_blue.front();
|
||||
trace << "DFS_BLUE treats: " << a_->format_state(f.s) << std::endl;
|
||||
if (!f.it->done())
|
||||
{
|
||||
const state *s_prime = f.it->dst();
|
||||
trace << " Visit the successor: "
|
||||
<< a_->format_state(s_prime) << std::endl;
|
||||
bdd label = f.it->cond();
|
||||
auto acc = f.it->acc();
|
||||
// Go down the edge (f.s, <label, acc>, s_prime)
|
||||
f.it->next();
|
||||
inc_transitions();
|
||||
typename heap::color_ref c = h.get_color_ref(s_prime);
|
||||
if (c.is_white())
|
||||
{
|
||||
trace << " It is white, go down" << std::endl;
|
||||
inc_states();
|
||||
h.add_new_state(s_prime, CYAN);
|
||||
push(st_blue, s_prime, label, acc);
|
||||
}
|
||||
else if (c.get_color() == CYAN && (a_->acc().accepting(acc) ||
|
||||
(f.s->compare(s_prime) != 0
|
||||
&& a_->acc().accepting(f.acc))))
|
||||
{
|
||||
trace << " It is cyan and acceptance condition "
|
||||
<< "is reached, report cycle" << std::endl;
|
||||
c.set_color(RED);
|
||||
push(st_red, s_prime, label, acc);
|
||||
return true;
|
||||
}
|
||||
else if (a_->acc().accepting(acc) && c.get_color() != RED)
|
||||
{
|
||||
// the test 'c.get_color() != RED' is added to limit
|
||||
// the number of runs reported by successive
|
||||
// calls to the check method. Without this
|
||||
// functionnality, the test can be ommited.
|
||||
trace << " It is cyan or blue and the arc is "
|
||||
<< "accepting, start a red dfs" << std::endl;
|
||||
c.set_color(RED);
|
||||
push(st_red, s_prime, label, acc);
|
||||
if (dfs_red())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << " It is cyan, blue or red, pop it" << std::endl;
|
||||
h.pop_notify(s_prime);
|
||||
}
|
||||
}
|
||||
else
|
||||
// Backtrack the edge
|
||||
// (predecessor of f.s in st_blue, <f.label, f.acc>, f.s)
|
||||
{
|
||||
trace << " All the successors have been visited" << std::endl;
|
||||
stack_item f_dest(f);
|
||||
pop(st_blue);
|
||||
typename heap::color_ref c = h.get_color_ref(f_dest.s);
|
||||
assert(!c.is_white());
|
||||
if (!st_blue.empty() &&
|
||||
a_->acc().accepting(f_dest.acc) && c.get_color() != RED)
|
||||
{
|
||||
// the test 'c.get_color() != RED' is added to limit
|
||||
// the number of runs reported by successive
|
||||
// calls to the check method. Without this
|
||||
// functionnality, the test can be ommited.
|
||||
trace << " The arc from "
|
||||
<< a_->format_state(st_blue.front().s)
|
||||
<< " to the current state is accepting, start a "
|
||||
<< "red dfs" << std::endl;
|
||||
c.set_color(RED);
|
||||
push(st_red, f_dest.s, f_dest.label, f_dest.acc);
|
||||
if (dfs_red())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << " Pop it" << std::endl;
|
||||
c.set_color(BLUE);
|
||||
h.pop_notify(f_dest.s);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dfs_red()
|
||||
{
|
||||
assert(!st_red.empty());
|
||||
|
||||
while (!st_red.empty())
|
||||
{
|
||||
stack_item& f = st_red.front();
|
||||
trace << "DFS_RED treats: " << a_->format_state(f.s) << std::endl;
|
||||
if (!f.it->done())
|
||||
{
|
||||
const state *s_prime = f.it->dst();
|
||||
trace << " Visit the successor: "
|
||||
<< a_->format_state(s_prime) << std::endl;
|
||||
bdd label = f.it->cond();
|
||||
auto acc = f.it->acc();
|
||||
// Go down the edge (f.s, <label, acc>, s_prime)
|
||||
f.it->next();
|
||||
inc_transitions();
|
||||
typename heap::color_ref c = h.get_color_ref(s_prime);
|
||||
if (c.is_white())
|
||||
{
|
||||
// For an explicit search, we can pose assert(!c.is_white())
|
||||
// because to reach a white state, the red dfs must
|
||||
// have crossed a cyan one (a state in the blue stack)
|
||||
// implying the report of a cycle.
|
||||
// However, with a bit-state hashing search and due to
|
||||
// collision, this property does not hold.
|
||||
trace << " It is white (due to collision), pop it"
|
||||
<< std::endl;
|
||||
s_prime->destroy();
|
||||
}
|
||||
else if (c.get_color() == RED)
|
||||
{
|
||||
trace << " It is red, pop it" << std::endl;
|
||||
h.pop_notify(s_prime);
|
||||
}
|
||||
else if (c.get_color() == CYAN)
|
||||
{
|
||||
trace << " It is cyan, report a cycle" << std::endl;
|
||||
c.set_color(RED);
|
||||
push(st_red, s_prime, label, acc);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace << " It is blue, go down" << std::endl;
|
||||
c.set_color(RED);
|
||||
push(st_red, s_prime, label, acc);
|
||||
}
|
||||
}
|
||||
else // Backtrack
|
||||
{
|
||||
trace << " All the successors have been visited, pop it"
|
||||
<< std::endl;
|
||||
h.pop_notify(f.s);
|
||||
pop(st_red);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class result_from_stack: public emptiness_check_result,
|
||||
public acss_statistics
|
||||
{
|
||||
public:
|
||||
result_from_stack(const std::shared_ptr<se05_search>& ms)
|
||||
: emptiness_check_result(ms->automaton()), ms_(ms)
|
||||
{
|
||||
}
|
||||
|
||||
virtual twa_run_ptr accepting_run()
|
||||
{
|
||||
assert(!ms_->st_blue.empty());
|
||||
assert(!ms_->st_red.empty());
|
||||
|
||||
auto run = std::make_shared<twa_run>(automaton());
|
||||
|
||||
typename stack_type::const_reverse_iterator i, j, end;
|
||||
twa_run::steps* l;
|
||||
|
||||
const state* target = ms_->st_red.front().s;
|
||||
|
||||
l = &run->prefix;
|
||||
|
||||
i = ms_->st_blue.rbegin();
|
||||
end = ms_->st_blue.rend(); --end;
|
||||
j = i; ++j;
|
||||
for (; i != end; ++i, ++j)
|
||||
{
|
||||
if (l == &run->prefix && i->s->compare(target) == 0)
|
||||
l = &run->cycle;
|
||||
twa_run::step s = { i->s->clone(), j->label, j->acc };
|
||||
l->push_back(s);
|
||||
}
|
||||
|
||||
if (l == &run->prefix && i->s->compare(target) == 0)
|
||||
l = &run->cycle;
|
||||
assert(l == &run->cycle);
|
||||
|
||||
j = ms_->st_red.rbegin();
|
||||
twa_run::step s = { i->s->clone(), j->label, j->acc };
|
||||
l->push_back(s);
|
||||
|
||||
i = j; ++j;
|
||||
end = ms_->st_red.rend(); --end;
|
||||
for (; i != end; ++i, ++j)
|
||||
{
|
||||
twa_run::step s = { i->s->clone(), j->label, j->acc };
|
||||
l->push_back(s);
|
||||
}
|
||||
|
||||
return run;
|
||||
}
|
||||
|
||||
unsigned acss_states() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<se05_search> ms_;
|
||||
};
|
||||
|
||||
# define FROM_STACK "ar:from_stack"
|
||||
|
||||
class se05_result: public emptiness_check_result
|
||||
{
|
||||
public:
|
||||
se05_result(const std::shared_ptr<se05_search>& m,
|
||||
option_map o = option_map())
|
||||
: emptiness_check_result(m->automaton(), o), ms(m)
|
||||
{
|
||||
if (options()[FROM_STACK])
|
||||
computer = new result_from_stack(ms);
|
||||
else
|
||||
computer = new ndfs_result<se05_search<heap>, heap>(ms);
|
||||
}
|
||||
|
||||
virtual void options_updated(const option_map& old)
|
||||
{
|
||||
if (old[FROM_STACK] && !options()[FROM_STACK])
|
||||
{
|
||||
delete computer;
|
||||
computer = new ndfs_result<se05_search<heap>, heap>(ms);
|
||||
}
|
||||
else if (!old[FROM_STACK] && options()[FROM_STACK])
|
||||
{
|
||||
delete computer;
|
||||
computer = new result_from_stack(ms);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~se05_result()
|
||||
{
|
||||
delete computer;
|
||||
}
|
||||
|
||||
virtual twa_run_ptr accepting_run()
|
||||
{
|
||||
return computer->accepting_run();
|
||||
}
|
||||
|
||||
virtual const unsigned_statistics* statistics() const
|
||||
{
|
||||
return computer->statistics();
|
||||
}
|
||||
|
||||
private:
|
||||
emptiness_check_result* computer;
|
||||
std::shared_ptr<se05_search> ms;
|
||||
};
|
||||
};
|
||||
|
||||
class explicit_se05_search_heap
|
||||
{
|
||||
typedef std::unordered_set<const state*,
|
||||
state_ptr_hash, state_ptr_equal> hcyan_type;
|
||||
typedef std::unordered_map<const state*, color,
|
||||
state_ptr_hash, state_ptr_equal> hash_type;
|
||||
public:
|
||||
enum { Safe = 1 };
|
||||
|
||||
class color_ref
|
||||
{
|
||||
public:
|
||||
color_ref(hash_type* h, hcyan_type* hc, const state* s)
|
||||
: is_cyan(true), ph(h), phc(hc), ps(s), pc(nullptr)
|
||||
{
|
||||
}
|
||||
color_ref(color* c)
|
||||
: is_cyan(false), ph(nullptr), phc(nullptr), ps(nullptr), pc(c)
|
||||
{
|
||||
}
|
||||
color get_color() const
|
||||
{
|
||||
if (is_cyan)
|
||||
return CYAN;
|
||||
return *pc;
|
||||
}
|
||||
void set_color(color c)
|
||||
{
|
||||
assert(!is_white());
|
||||
if (is_cyan)
|
||||
{
|
||||
assert(c != CYAN);
|
||||
int i = phc->erase(ps);
|
||||
assert(i == 1);
|
||||
(void)i;
|
||||
ph->emplace(ps, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
*pc=c;
|
||||
}
|
||||
}
|
||||
bool is_white() const
|
||||
{
|
||||
return !is_cyan && !pc;
|
||||
}
|
||||
private:
|
||||
bool is_cyan;
|
||||
hash_type* ph; //point to the main hash table
|
||||
hcyan_type* phc; // point to the hash table hcyan
|
||||
const state* ps; // point to the state in hcyan
|
||||
color *pc; // point to the color of a state stored in main hash table
|
||||
};
|
||||
|
||||
explicit_se05_search_heap(size_t)
|
||||
{
|
||||
}
|
||||
|
||||
~explicit_se05_search_heap()
|
||||
{
|
||||
hcyan_type::const_iterator sc = hc.begin();
|
||||
while (sc != hc.end())
|
||||
{
|
||||
const state* ptr = *sc;
|
||||
++sc;
|
||||
ptr->destroy();
|
||||
}
|
||||
hash_type::const_iterator s = h.begin();
|
||||
while (s != h.end())
|
||||
{
|
||||
const state* ptr = s->first;
|
||||
++s;
|
||||
ptr->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
color_ref get_color_ref(const state*& s)
|
||||
{
|
||||
hcyan_type::iterator ic = hc.find(s);
|
||||
if (ic == hc.end())
|
||||
{
|
||||
hash_type::iterator it = h.find(s);
|
||||
if (it == h.end())
|
||||
return color_ref(nullptr); // white state
|
||||
if (s != it->first)
|
||||
{
|
||||
s->destroy();
|
||||
s = it->first;
|
||||
}
|
||||
return color_ref(&it->second); // blue or red state
|
||||
}
|
||||
if (s != *ic)
|
||||
{
|
||||
s->destroy();
|
||||
s = *ic;
|
||||
}
|
||||
return color_ref(&h, &hc, *ic); // cyan state
|
||||
}
|
||||
|
||||
void add_new_state(const state* s, color c)
|
||||
{
|
||||
assert(hc.find(s) == hc.end() && h.find(s) == h.end());
|
||||
if (c == CYAN)
|
||||
hc.insert(s);
|
||||
else
|
||||
h.emplace(s, c);
|
||||
}
|
||||
|
||||
void pop_notify(const state*) const
|
||||
{
|
||||
}
|
||||
|
||||
bool has_been_visited(const state* s) const
|
||||
{
|
||||
hcyan_type::const_iterator ic = hc.find(s);
|
||||
if (ic == hc.end())
|
||||
{
|
||||
hash_type::const_iterator it = h.find(s);
|
||||
return (it != h.end());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum { Has_Size = 1 };
|
||||
int size() const
|
||||
{
|
||||
return h.size() + hc.size();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
hash_type h; // associate to each blue and red state its color
|
||||
hcyan_type hc; // associate to each cyan state its weight
|
||||
};
|
||||
|
||||
class bsh_se05_search_heap
|
||||
{
|
||||
private:
|
||||
typedef std::unordered_set<const state*,
|
||||
state_ptr_hash, state_ptr_equal> hcyan_type;
|
||||
public:
|
||||
enum { Safe = 0 };
|
||||
|
||||
class color_ref
|
||||
{
|
||||
public:
|
||||
color_ref(hcyan_type* h, const state* st,
|
||||
unsigned char *base, unsigned char offset)
|
||||
: is_cyan(true), phc(h), ps(st), b(base), o(offset*2)
|
||||
{
|
||||
}
|
||||
color_ref(unsigned char *base, unsigned char offset)
|
||||
: is_cyan(false), phc(nullptr), ps(nullptr), b(base), o(offset*2)
|
||||
{
|
||||
}
|
||||
color get_color() const
|
||||
{
|
||||
if (is_cyan)
|
||||
return CYAN;
|
||||
return color(((*b) >> o) & 3U);
|
||||
}
|
||||
void set_color(color c)
|
||||
{
|
||||
if (is_cyan && c != CYAN)
|
||||
{
|
||||
int i = phc->erase(ps);
|
||||
assert(i == 1);
|
||||
(void)i;
|
||||
}
|
||||
*b = (*b & ~(3U << o)) | (c << o);
|
||||
}
|
||||
bool is_white() const
|
||||
{
|
||||
return get_color() == WHITE;
|
||||
}
|
||||
private:
|
||||
bool is_cyan;
|
||||
hcyan_type* phc;
|
||||
const state* ps;
|
||||
unsigned char *b;
|
||||
unsigned char o;
|
||||
};
|
||||
|
||||
bsh_se05_search_heap(size_t s) : size_(s)
|
||||
{
|
||||
h = new unsigned char[size_];
|
||||
memset(h, WHITE, size_);
|
||||
}
|
||||
|
||||
~bsh_se05_search_heap()
|
||||
{
|
||||
delete[] h;
|
||||
}
|
||||
|
||||
color_ref get_color_ref(const state*& s)
|
||||
{
|
||||
size_t ha = s->hash();
|
||||
hcyan_type::iterator ic = hc.find(s);
|
||||
if (ic != hc.end())
|
||||
return color_ref(&hc, *ic, &h[ha%size_], ha%4);
|
||||
return color_ref(&h[ha%size_], ha%4);
|
||||
}
|
||||
|
||||
void add_new_state(const state* s, color c)
|
||||
{
|
||||
assert(get_color_ref(s).is_white());
|
||||
if (c == CYAN)
|
||||
hc.insert(s);
|
||||
else
|
||||
{
|
||||
color_ref cr(get_color_ref(s));
|
||||
cr.set_color(c);
|
||||
}
|
||||
}
|
||||
|
||||
void pop_notify(const state* s) const
|
||||
{
|
||||
s->destroy();
|
||||
}
|
||||
|
||||
bool has_been_visited(const state* s) const
|
||||
{
|
||||
hcyan_type::const_iterator ic = hc.find(s);
|
||||
if (ic != hc.end())
|
||||
return true;
|
||||
size_t ha = s->hash();
|
||||
return color((h[ha%size_] >> ((ha%4)*2)) & 3U) != WHITE;
|
||||
}
|
||||
|
||||
enum { Has_Size = 0 };
|
||||
|
||||
private:
|
||||
size_t size_;
|
||||
unsigned char* h;
|
||||
hcyan_type hc;
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
emptiness_check_ptr
|
||||
explicit_se05_search(const const_twa_ptr& a, option_map o)
|
||||
{
|
||||
return std::make_shared<se05_search<explicit_se05_search_heap>>(a, 0, o);
|
||||
}
|
||||
|
||||
emptiness_check_ptr
|
||||
bit_state_hashing_se05_search(const const_twa_ptr& a,
|
||||
size_t size, option_map o)
|
||||
{
|
||||
return std::make_shared<se05_search<bsh_se05_search_heap>>(a, size, o);
|
||||
}
|
||||
|
||||
emptiness_check_ptr
|
||||
se05(const const_twa_ptr& a, option_map o)
|
||||
{
|
||||
size_t size = o.get("bsh");
|
||||
if (size)
|
||||
return bit_state_hashing_se05_search(a, size, o);
|
||||
return explicit_se05_search(a, o);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2013, 2014 Laboratoire de Recherche et Developpement
|
||||
// de l'Epita (LRDE).
|
||||
// Copyright (C) 2004, 2005 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 <cstddef>
|
||||
#include "misc/optionmap.hh"
|
||||
#include "twa/fwd.hh"
|
||||
#include "emptiness.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup emptiness_check_algorithms
|
||||
/// @{
|
||||
|
||||
/// \brief Returns an emptiness check on the spot::tgba automaton \a a.
|
||||
///
|
||||
/// \pre The automaton \a a must have at most one acceptance condition (i.e.
|
||||
/// it is a TBA).
|
||||
///
|
||||
/// During the visit of \a a, the returned checker stores explicitely all
|
||||
/// the traversed states.
|
||||
/// The method \a check() of the checker can be called several times
|
||||
/// (until it returns a null pointer) to enumerate all the visited accepting
|
||||
/// paths. The implemented algorithm is an optimization of
|
||||
/// spot::explicit_magic_search and is the following:
|
||||
///
|
||||
/** \verbatim
|
||||
procedure check ()
|
||||
begin
|
||||
call dfs_blue(s0);
|
||||
end;
|
||||
|
||||
procedure dfs_blue (s)
|
||||
begin
|
||||
s.color = cyan;
|
||||
for all t in post(s) do
|
||||
if t.color == white then
|
||||
call dfs_blue(t);
|
||||
else if t.color == cyan and
|
||||
(the edge (s,t) is accepting or
|
||||
(it exists a predecessor p of s in st_blue and s != t and
|
||||
the arc between p and s is accepting)) then
|
||||
report cycle;
|
||||
end if;
|
||||
if the edge (s,t) is accepting then
|
||||
call dfs_red(t);
|
||||
end if;
|
||||
end for;
|
||||
s.color = blue;
|
||||
end;
|
||||
|
||||
procedure dfs_red(s)
|
||||
begin
|
||||
if s.color == cyan then
|
||||
report cycle;
|
||||
end if;
|
||||
s.color = red;
|
||||
for all t in post(s) do
|
||||
if t.color == blue then
|
||||
call dfs_red(t);
|
||||
end if;
|
||||
end for;
|
||||
end;
|
||||
\endverbatim */
|
||||
///
|
||||
/// It is an adaptation to TBA of the one presented in
|
||||
/** \verbatim
|
||||
@techreport{SE04,
|
||||
author = {Stefan Schwoon and Javier Esparza},
|
||||
institution = {Universit{\"a}t Stuttgart, Fakult\"at Informatik,
|
||||
Elektrotechnik und Informationstechnik},
|
||||
month = {November},
|
||||
number = {2004/06},
|
||||
title = {A Note on On-The-Fly Verification Algorithms},
|
||||
year = {2004},
|
||||
url =
|
||||
{http://www.fmi.uni-stuttgart.de/szs/publications/info/schwoosn.SE04.shtml}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// \sa spot::explicit_magic_search
|
||||
///
|
||||
SPOT_API emptiness_check_ptr
|
||||
explicit_se05_search(const const_twa_ptr& a, option_map o = option_map());
|
||||
|
||||
/// \brief Returns an emptiness checker on the spot::tgba automaton \a a.
|
||||
///
|
||||
/// \pre The automaton \a a must have at most one acceptance condition (i.e.
|
||||
/// it is a TBA).
|
||||
///
|
||||
/// During the visit of \a a, the returned checker does not store explicitely
|
||||
/// the traversed states but uses the bit-state hashing technic presented in:
|
||||
///
|
||||
/** \verbatim
|
||||
@book{Holzmann91,
|
||||
author = {G.J. Holzmann},
|
||||
title = {Design and Validation of Computer Protocols},
|
||||
publisher = {Prentice-Hall},
|
||||
address = {Englewood Cliffs, New Jersey},
|
||||
year = {1991}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// Consequently, the detection of an acceptence cycle is not ensured.
|
||||
///
|
||||
/// The size of the heap is limited to \n size bytes.
|
||||
///
|
||||
/// The implemented algorithm is the same as the one of
|
||||
/// spot::explicit_se05_search.
|
||||
///
|
||||
/// \sa spot::explicit_se05_search
|
||||
///
|
||||
SPOT_API emptiness_check_ptr
|
||||
bit_state_hashing_se05_search(const const_twa_ptr& a, size_t size,
|
||||
option_map o = option_map());
|
||||
|
||||
|
||||
/// \brief Wrapper for the two se05 implementations.
|
||||
///
|
||||
/// This wrapper calls explicit_se05_search() or
|
||||
/// bit_state_hashing_se05_search() according to the \c "bsh" option
|
||||
/// in the \c option_map. If \c "bsh" is set and non null, its value
|
||||
/// is used as the size of the hash map.
|
||||
SPOT_API emptiness_check_ptr
|
||||
se05(const const_twa_ptr& a, option_map o);
|
||||
|
||||
/// @}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et
|
||||
// Développement de l'Epita.
|
||||
//
|
||||
// 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 "sepsets.hh"
|
||||
#include "sccinfo.hh"
|
||||
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static acc_cond::mark_t common_sets(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
auto p = aut->get_acceptance().used_inf_fin_sets();
|
||||
return p.first & p.second;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_separate_sets(const const_twa_graph_ptr& aut)
|
||||
{
|
||||
return common_sets(aut) == 0U;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
separate_sets_here(const twa_graph_ptr& aut)
|
||||
{
|
||||
auto common = common_sets(aut);
|
||||
if (common == 0U)
|
||||
return aut;
|
||||
// Each Fin(first) should be replaced by Fin(second).
|
||||
std::vector<std::pair<acc_cond::mark_t, acc_cond::mark_t>> map;
|
||||
{
|
||||
unsigned base = aut->acc().add_sets(common.count());
|
||||
for (auto s: common.sets())
|
||||
map.emplace_back(acc_cond::mark_t({s}),
|
||||
acc_cond::mark_t({base++}));
|
||||
}
|
||||
|
||||
// Fix the acceptance condition
|
||||
auto& code = aut->acc().get_acceptance();
|
||||
// If code were empty, then common would have been 0.
|
||||
assert (!code.empty());
|
||||
acc_cond::acc_word* pos = &code.back();
|
||||
acc_cond::acc_word* start = &code.front();
|
||||
while (pos > start)
|
||||
{
|
||||
switch (pos->op)
|
||||
{
|
||||
case acc_cond::acc_op::Or:
|
||||
case acc_cond::acc_op::And:
|
||||
--pos;
|
||||
break;
|
||||
case acc_cond::acc_op::Fin:
|
||||
case acc_cond::acc_op::FinNeg:
|
||||
if ((pos[-1].mark & common) == 0U)
|
||||
break;
|
||||
for (auto p: map)
|
||||
if (pos[-1].mark & p.first)
|
||||
{
|
||||
pos[-1].mark -= p.first;
|
||||
pos[-1].mark |= p.second;
|
||||
}
|
||||
/* fall through */
|
||||
case acc_cond::acc_op::Inf:
|
||||
case acc_cond::acc_op::InfNeg:
|
||||
pos -= 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix the edges
|
||||
for (auto& t: aut->edges())
|
||||
{
|
||||
if ((t.acc & common) == 0U)
|
||||
continue;
|
||||
for (auto p: map)
|
||||
if (t.acc & p.first)
|
||||
t.acc |= p.second;
|
||||
}
|
||||
return aut;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2015 Laboratoire de Recherche et Développement
|
||||
// de l'Epita.
|
||||
//
|
||||
// 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 "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief Whether the Inf and Fin numbers are disjoints
|
||||
SPOT_API bool
|
||||
has_separate_sets(const const_twa_graph_ptr& aut);
|
||||
|
||||
/// \brief Separate the Fin and Inf sets used by an automaton
|
||||
///
|
||||
/// This makes sure that the numbers used a Fin and Inf are
|
||||
/// disjoints.
|
||||
SPOT_API twa_graph_ptr
|
||||
separate_sets_here(const twa_graph_ptr& aut);
|
||||
}
|
||||
|
|
@ -1,802 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "simulation.hh"
|
||||
#include "misc/minato.hh"
|
||||
#include "twa/bddprint.hh"
|
||||
#include "twaalgos/reachiter.hh"
|
||||
#include "twaalgos/sccfilter.hh"
|
||||
#include "twaalgos/sccinfo.hh"
|
||||
#include "twaalgos/sepsets.hh"
|
||||
#include "misc/bddlt.hh"
|
||||
|
||||
// The way we developed this algorithm is the following: We take an
|
||||
// automaton, and reverse all these acceptance conditions. We reverse
|
||||
// them to go make the meaning of the signature easier. We are using
|
||||
// bdd, and we want to let it make all the simplification. Because of
|
||||
// the format of the acceptance condition, it doesn't allow easy
|
||||
// simplification. Instead of encoding them as: "a!b!c + !ab!c", we
|
||||
// use them as: "ab". We complement them because we want a
|
||||
// simplification if the condition of the edge A implies the
|
||||
// edge of B, and if the acceptance condition of A is included
|
||||
// in the acceptance condition of B. To let the bdd makes the job, we
|
||||
// revert them.
|
||||
|
||||
// Then, to check if a edge i-dominates another, we'll use the bdd:
|
||||
// "sig(transA) = cond(trans) & acc(trans) & implied(class(trans->state))"
|
||||
// Idem for sig(transB). The 'implied'
|
||||
// (represented by a hash table 'relation_' in the implementation) is
|
||||
// a conjunction of all the class dominated by the class of the
|
||||
// destination. This is how the relation is included in the
|
||||
// signature. It makes the simplifications alone, and the work is
|
||||
// done. The algorithm is cut into several step:
|
||||
//
|
||||
// 1. Run through the tgba and switch the acceptance condition to their
|
||||
// negation, and initializing relation_ by the 'init_ -> init_' where
|
||||
// init_ is the bdd which represents the class. This function is the
|
||||
// constructor of Simulation.
|
||||
// 2. Enter in the loop (run).
|
||||
// - Rename the class.
|
||||
// - run through the automaton and computing the signature of each
|
||||
// state. This function is `update_sig'.
|
||||
// - Enter in a double loop to adapt the partial order, and set
|
||||
// 'relation_' accordingly. This function is `update_po'.
|
||||
// 3. Rename the class (to actualize the name in the previous_class and
|
||||
// in relation_).
|
||||
// 4. Building an automaton with the result, with the condition:
|
||||
// "a edge in the original automaton appears in the simulated one
|
||||
// iff this edge is included in the set of i-maximal neighbour."
|
||||
// This function is `build_output'.
|
||||
// The automaton simulated is recomplemented to come back to its initial
|
||||
// state when the object Simulation is destroyed.
|
||||
//
|
||||
// Obviously these functions are possibly cut into several little ones.
|
||||
// This is just the general development idea.
|
||||
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Some useful typedef:
|
||||
|
||||
// Used to get the signature of the state.
|
||||
typedef std::vector<bdd> vector_state_bdd;
|
||||
|
||||
// Get the list of state for each class.
|
||||
typedef std::map<bdd, std::list<unsigned>,
|
||||
bdd_less_than> map_bdd_lstate;
|
||||
|
||||
typedef std::map<bdd, unsigned, bdd_less_than> map_bdd_state;
|
||||
|
||||
|
||||
// This class helps to compare two automata in term of
|
||||
// size.
|
||||
struct automaton_size
|
||||
{
|
||||
automaton_size()
|
||||
: edges(0),
|
||||
states(0)
|
||||
{
|
||||
}
|
||||
|
||||
automaton_size(const twa_graph_ptr& a)
|
||||
: edges(a->num_edges()),
|
||||
states(a->num_states())
|
||||
{
|
||||
}
|
||||
|
||||
void set_size(const twa_graph_ptr& a)
|
||||
{
|
||||
states = a->num_states();
|
||||
edges = a->num_edges();
|
||||
}
|
||||
|
||||
inline bool operator!=(const automaton_size& r)
|
||||
{
|
||||
return edges != r.edges || states != r.states;
|
||||
}
|
||||
|
||||
inline bool operator<(const automaton_size& r)
|
||||
{
|
||||
if (states < r.states)
|
||||
return true;
|
||||
if (states > r.states)
|
||||
return false;
|
||||
|
||||
if (edges < r.edges)
|
||||
return true;
|
||||
if (edges > r.edges)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool operator>(const automaton_size& r)
|
||||
{
|
||||
if (states < r.states)
|
||||
return false;
|
||||
if (states > r.states)
|
||||
return true;
|
||||
|
||||
if (edges < r.edges)
|
||||
return false;
|
||||
if (edges > r.edges)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int edges;
|
||||
int states;
|
||||
};
|
||||
|
||||
// The direct_simulation. If Cosimulation is true, we are doing a
|
||||
// cosimulation.
|
||||
template <bool Cosimulation, bool Sba>
|
||||
class direct_simulation
|
||||
{
|
||||
protected:
|
||||
// Shortcut used in update_po and go_to_next_it.
|
||||
typedef std::map<bdd, bdd, bdd_less_than> map_bdd_bdd;
|
||||
int acc_vars;
|
||||
acc_cond::mark_t all_inf_;
|
||||
public:
|
||||
|
||||
bdd mark_to_bdd(acc_cond::mark_t m)
|
||||
{
|
||||
// FIXME: Use a cache.
|
||||
bdd res = bddtrue;
|
||||
for (auto n: a_->acc().sets(m))
|
||||
res &= bdd_ithvar(acc_vars + n);
|
||||
return res;
|
||||
}
|
||||
|
||||
acc_cond::mark_t bdd_to_mark(const twa_graph_ptr& aut, bdd b)
|
||||
{
|
||||
// FIXME: Use a cache.
|
||||
std::vector<unsigned> res;
|
||||
while (b != bddtrue)
|
||||
{
|
||||
res.push_back(bdd_var(b) - acc_vars);
|
||||
b = bdd_high(b);
|
||||
}
|
||||
return aut->acc().marks(res.begin(), res.end());
|
||||
}
|
||||
|
||||
direct_simulation(const const_twa_graph_ptr& in)
|
||||
: po_size_(0),
|
||||
all_class_var_(bddtrue),
|
||||
original_(in)
|
||||
{
|
||||
if (!has_separate_sets(in))
|
||||
throw std::runtime_error
|
||||
("direct_simulation() requires separate Inf and Fin sets");
|
||||
|
||||
// Call get_init_state_number() before anything else as it
|
||||
// might add a state.
|
||||
unsigned init_state_number = in->get_init_state_number();
|
||||
scc_info_.reset(new scc_info(in));
|
||||
|
||||
unsigned ns = in->num_states();
|
||||
assert(ns > 0);
|
||||
size_a_ = ns;
|
||||
|
||||
auto all_inf = in->get_acceptance().used_inf_fin_sets().first;
|
||||
all_inf_ = all_inf;
|
||||
|
||||
// Replace all the acceptance conditions by their complements.
|
||||
// (In the case of Cosimulation, we also flip the edges.)
|
||||
if (Cosimulation)
|
||||
{
|
||||
a_ = make_twa_graph(in->get_dict());
|
||||
a_->copy_ap_of(in);
|
||||
a_->copy_acceptance_of(in);
|
||||
a_->new_states(ns);
|
||||
|
||||
for (unsigned s = 0; s < ns; ++s)
|
||||
{
|
||||
for (auto& t: in->out(s))
|
||||
{
|
||||
acc_cond::mark_t acc;
|
||||
if (Sba)
|
||||
{
|
||||
// If the acceptance is interpreted as
|
||||
// state-based, to apply the reverse simulation
|
||||
// on a SBA, we should pull the acceptance of
|
||||
// the destination state on its incoming arcs
|
||||
// (which now become outgoing arcs after
|
||||
// transposition).
|
||||
acc = 0U;
|
||||
for (auto& td: in->out(t.dst))
|
||||
{
|
||||
acc = td.acc ^ all_inf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
acc = t.acc ^ all_inf;
|
||||
}
|
||||
a_->new_edge(t.dst, s, t.cond, acc);
|
||||
}
|
||||
a_->set_init_state(init_state_number);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
a_ = make_twa_graph(in, twa::prop_set::all());
|
||||
for (auto& t: a_->edges())
|
||||
t.acc ^= all_inf;
|
||||
}
|
||||
assert(a_->num_states() == size_a_);
|
||||
|
||||
// Now, we have to get the bdd which will represent the
|
||||
// class. We register one bdd by state, because in the worst
|
||||
// case, |Class| == |State|.
|
||||
unsigned set_num = a_->get_dict()
|
||||
->register_anonymous_variables(size_a_ + 1, this);
|
||||
|
||||
unsigned n_acc = a_->num_sets();
|
||||
acc_vars = a_->get_dict()
|
||||
->register_anonymous_variables(n_acc, this);
|
||||
|
||||
all_proms_ = bddtrue;
|
||||
for (unsigned v = acc_vars; v < acc_vars + n_acc; ++v)
|
||||
all_proms_ &= bdd_ithvar(v);
|
||||
|
||||
bdd_initial = bdd_ithvar(set_num++);
|
||||
bdd init = bdd_ithvar(set_num++);
|
||||
|
||||
used_var_.push_back(init);
|
||||
|
||||
// Initialize all classes to init.
|
||||
previous_class_.resize(size_a_);
|
||||
for (unsigned s = 0; s < size_a_; ++s)
|
||||
previous_class_[s] = init;
|
||||
|
||||
// Put all the anonymous variable in a queue, and record all
|
||||
// of these in a variable all_class_var_ which will be used
|
||||
// to understand the destination part in the signature when
|
||||
// building the resulting automaton.
|
||||
all_class_var_ = init;
|
||||
for (unsigned i = set_num; i < set_num + size_a_ - 1; ++i)
|
||||
{
|
||||
free_var_.push(i);
|
||||
all_class_var_ &= bdd_ithvar(i);
|
||||
}
|
||||
|
||||
relation_[init] = init;
|
||||
}
|
||||
|
||||
|
||||
// Reverse all the acceptance condition at the destruction of
|
||||
// this object, because it occurs after the return of the
|
||||
// function simulation.
|
||||
virtual ~direct_simulation()
|
||||
{
|
||||
a_->get_dict()->unregister_all_my_variables(this);
|
||||
}
|
||||
|
||||
// Update the name of the classes.
|
||||
void update_previous_class()
|
||||
{
|
||||
std::list<bdd>::iterator it_bdd = used_var_.begin();
|
||||
|
||||
// We run through the map bdd/list<state>, and we update
|
||||
// the previous_class_ with the new data.
|
||||
for (auto& p: bdd_lstate_)
|
||||
{
|
||||
// If the signature of a state is bddfalse (no
|
||||
// edges) the class of this state is bddfalse
|
||||
// instead of an anonymous variable. It allows
|
||||
// simplifications in the signature by removing a
|
||||
// edge which has as a destination a state with
|
||||
// no outgoing edge.
|
||||
if (p.first == bddfalse)
|
||||
for (auto s: p.second)
|
||||
previous_class_[s] = bddfalse;
|
||||
else
|
||||
for (auto s: p.second)
|
||||
previous_class_[s] = *it_bdd;
|
||||
++it_bdd;
|
||||
}
|
||||
}
|
||||
|
||||
void main_loop()
|
||||
{
|
||||
unsigned int nb_partition_before = 0;
|
||||
unsigned int nb_po_before = po_size_ - 1;
|
||||
while (nb_partition_before != bdd_lstate_.size()
|
||||
|| nb_po_before != po_size_)
|
||||
{
|
||||
update_previous_class();
|
||||
nb_partition_before = bdd_lstate_.size();
|
||||
bdd_lstate_.clear();
|
||||
nb_po_before = po_size_;
|
||||
po_size_ = 0;
|
||||
update_sig();
|
||||
go_to_next_it();
|
||||
}
|
||||
|
||||
update_previous_class();
|
||||
}
|
||||
|
||||
// The core loop of the algorithm.
|
||||
twa_graph_ptr run()
|
||||
{
|
||||
main_loop();
|
||||
return build_result();
|
||||
}
|
||||
|
||||
// Take a state and compute its signature.
|
||||
bdd compute_sig(unsigned src)
|
||||
{
|
||||
bdd res = bddfalse;
|
||||
|
||||
for (auto& t: a_->out(src))
|
||||
{
|
||||
bdd acc = mark_to_bdd(t.acc);
|
||||
|
||||
// to_add is a conjunction of the acceptance condition,
|
||||
// the label of the edge and the class of the
|
||||
// destination and all the class it implies.
|
||||
bdd to_add = acc & t.cond & relation_[previous_class_[t.dst]];
|
||||
|
||||
res |= to_add;
|
||||
}
|
||||
|
||||
// When we Cosimulate, we add a special flag to differentiate
|
||||
// the initial state from the other.
|
||||
if (Cosimulation && src == a_->get_init_state_number())
|
||||
res |= bdd_initial;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void update_sig()
|
||||
{
|
||||
for (unsigned s = 0; s < size_a_; ++s)
|
||||
bdd_lstate_[compute_sig(s)].push_back(s);
|
||||
}
|
||||
|
||||
|
||||
// This method rename the color set, update the partial order.
|
||||
void go_to_next_it()
|
||||
{
|
||||
int nb_new_color = bdd_lstate_.size() - used_var_.size();
|
||||
|
||||
|
||||
// If we have created more partitions, we need to use more
|
||||
// variables.
|
||||
for (int i = 0; i < nb_new_color; ++i)
|
||||
{
|
||||
assert(!free_var_.empty());
|
||||
used_var_.push_back(bdd_ithvar(free_var_.front()));
|
||||
free_var_.pop();
|
||||
}
|
||||
|
||||
|
||||
// If we have reduced the number of partition, we 'free' them
|
||||
// in the free_var_ list.
|
||||
for (int i = 0; i > nb_new_color; --i)
|
||||
{
|
||||
assert(!used_var_.empty());
|
||||
free_var_.push(bdd_var(used_var_.front()));
|
||||
used_var_.pop_front();
|
||||
}
|
||||
|
||||
|
||||
assert((bdd_lstate_.size() == used_var_.size())
|
||||
|| (bdd_lstate_.find(bddfalse) != bdd_lstate_.end()
|
||||
&& bdd_lstate_.size() == used_var_.size() + 1));
|
||||
|
||||
// Now we make a temporary hash_table which links the tuple
|
||||
// "C^(i-1), N^(i-1)" to the new class coloring. If we
|
||||
// rename the class before updating the partial order, we
|
||||
// loose the information, and if we make it after, I can't
|
||||
// figure out how to apply this renaming on rel_.
|
||||
// It adds a data structure but it solves our problem.
|
||||
map_bdd_bdd now_to_next;
|
||||
|
||||
std::list<bdd>::iterator it_bdd = used_var_.begin();
|
||||
|
||||
for (auto& p: bdd_lstate_)
|
||||
{
|
||||
// If the signature of a state is bddfalse (no
|
||||
// edges) the class of this state is bddfalse
|
||||
// instead of an anonymous variable. It allows
|
||||
// simplifications in the signature by removing a
|
||||
// edge which has as a destination a state with
|
||||
// no outgoing edge.
|
||||
bdd acc = bddfalse;
|
||||
if (p.first != bddfalse)
|
||||
acc = *it_bdd;
|
||||
now_to_next[p.first] = acc;
|
||||
++it_bdd;
|
||||
}
|
||||
|
||||
update_po(now_to_next, relation_);
|
||||
}
|
||||
|
||||
// This function computes the new po with previous_class_ and
|
||||
// the argument. `now_to_next' contains the relation between the
|
||||
// signature and the future name of the class. We need a
|
||||
// template parameter because we use this function with a
|
||||
// map_bdd_bdd, but later, we need a list_bdd_bdd. So to
|
||||
// factorize some code we use a template.
|
||||
template <typename container_bdd_bdd>
|
||||
void update_po(const container_bdd_bdd& now_to_next,
|
||||
map_bdd_bdd& relation)
|
||||
{
|
||||
// This loop follows the pattern given by the paper.
|
||||
// foreach class do
|
||||
// | foreach class do
|
||||
// | | update po if needed
|
||||
// | od
|
||||
// od
|
||||
|
||||
for (typename container_bdd_bdd::const_iterator it1
|
||||
= now_to_next.begin();
|
||||
it1 != now_to_next.end();
|
||||
++it1)
|
||||
{
|
||||
bdd accu = it1->second;
|
||||
for (typename container_bdd_bdd::const_iterator it2
|
||||
= now_to_next.begin();
|
||||
it2 != now_to_next.end();
|
||||
++it2)
|
||||
{
|
||||
// Skip the case managed by the initialization of accu.
|
||||
if (it1 == it2)
|
||||
continue;
|
||||
|
||||
if (bdd_implies(it1->first, it2->first))
|
||||
{
|
||||
accu &= it2->second;
|
||||
++po_size_;
|
||||
}
|
||||
}
|
||||
relation[it1->second] = accu;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the minimal resulting automaton.
|
||||
twa_graph_ptr build_result()
|
||||
{
|
||||
twa_graph_ptr res = make_twa_graph(a_->get_dict());
|
||||
res->copy_ap_of(a_);
|
||||
res->copy_acceptance_of(a_);
|
||||
|
||||
// Non atomic propositions variables (= acc and class)
|
||||
bdd nonapvars = all_proms_ & bdd_support(all_class_var_);
|
||||
|
||||
auto* gb = res->create_namer<int>();
|
||||
|
||||
// Create one state per partition.
|
||||
for (auto& p: bdd_lstate_)
|
||||
{
|
||||
bdd cl = previous_class_[p.second.front()];
|
||||
// A state may be referred to either by
|
||||
// its class, or by all the implied classes.
|
||||
auto s = gb->new_state(cl.id());
|
||||
gb->alias_state(s, relation_[cl].id());
|
||||
}
|
||||
|
||||
// Acceptance of states. Only used if Sba && Cosimulation.
|
||||
std::vector<acc_cond::mark_t> accst;
|
||||
if (Sba && Cosimulation)
|
||||
accst.resize(res->num_states(), 0U);
|
||||
|
||||
stat.states = bdd_lstate_.size();
|
||||
stat.edges = 0;
|
||||
|
||||
unsigned nb_satoneset = 0;
|
||||
unsigned nb_minato = 0;
|
||||
|
||||
auto all_inf = all_inf_;
|
||||
// For each class, we will create
|
||||
// all the edges between the states.
|
||||
for (auto& p: bdd_lstate_)
|
||||
{
|
||||
// All states in p.second have the same class, so just
|
||||
// pick the class of the first one first one.
|
||||
bdd src = previous_class_[p.second.front()];
|
||||
|
||||
// Get the signature to derive successors.
|
||||
bdd sig = compute_sig(p.second.front());
|
||||
|
||||
if (Cosimulation)
|
||||
sig = bdd_compose(sig, bddfalse, bdd_var(bdd_initial));
|
||||
|
||||
// Get all the variable in the signature.
|
||||
bdd sup_sig = bdd_support(sig);
|
||||
|
||||
// Get the variable in the signature which represents the
|
||||
// conditions.
|
||||
bdd sup_all_atomic_prop = bdd_exist(sup_sig, nonapvars);
|
||||
|
||||
// Get the part of the signature composed only with the atomic
|
||||
// proposition.
|
||||
bdd all_atomic_prop = bdd_exist(sig, nonapvars);
|
||||
|
||||
// First loop over all possible valuations atomic properties.
|
||||
while (all_atomic_prop != bddfalse)
|
||||
{
|
||||
bdd one = bdd_satoneset(all_atomic_prop,
|
||||
sup_all_atomic_prop,
|
||||
bddtrue);
|
||||
all_atomic_prop -= one;
|
||||
|
||||
// For each possible valuation, iterate over all possible
|
||||
// destination classes. We use minato_isop here, because
|
||||
// if the same valuation of atomic properties can go
|
||||
// to two different classes C1 and C2, iterating on
|
||||
// C1 + C2 with the above bdd_satoneset loop will see
|
||||
// C1 then (!C1)C2, instead of C1 then C2.
|
||||
// With minatop_isop, we ensure that the no negative
|
||||
// class variable will be seen (likewise for promises).
|
||||
minato_isop isop(sig & one);
|
||||
|
||||
++nb_satoneset;
|
||||
|
||||
bdd cond_acc_dest;
|
||||
while ((cond_acc_dest = isop.next()) != bddfalse)
|
||||
{
|
||||
++stat.edges;
|
||||
|
||||
++nb_minato;
|
||||
|
||||
// Take the edge, and keep only the variable which
|
||||
// are used to represent the class.
|
||||
bdd dst = bdd_existcomp(cond_acc_dest,
|
||||
all_class_var_);
|
||||
|
||||
// Keep only ones who are acceptance condition.
|
||||
auto acc = bdd_to_mark(res, bdd_existcomp(cond_acc_dest,
|
||||
all_proms_));
|
||||
|
||||
// Keep the other!
|
||||
bdd cond = bdd_existcomp(cond_acc_dest,
|
||||
sup_all_atomic_prop);
|
||||
|
||||
// Because we have complemented all the Inf
|
||||
// acceptance conditions on the input automaton,
|
||||
// we must revert them to create a new edge.
|
||||
acc ^= all_inf;
|
||||
|
||||
if (Cosimulation)
|
||||
{
|
||||
if (Sba)
|
||||
{
|
||||
// acc should be attached to src, or rather,
|
||||
// in our edge-based representation)
|
||||
// to all edges leaving src. As we
|
||||
// can't do this here, store this in a table
|
||||
// so we can fix it later.
|
||||
accst[gb->get_state(src.id())] = acc;
|
||||
acc = 0U;
|
||||
}
|
||||
gb->new_edge(dst.id(), src.id(), cond, acc);
|
||||
}
|
||||
else
|
||||
{
|
||||
gb->new_edge(src.id(), dst.id(), cond, acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res->set_init_state(gb->get_state(previous_class_
|
||||
[a_->get_init_state_number()].id()));
|
||||
|
||||
res->merge_edges(); // FIXME: is this really needed?
|
||||
|
||||
// Mark all accepting state in a second pass, when
|
||||
// dealing with SBA in cosimulation.
|
||||
if (Sba && Cosimulation)
|
||||
{
|
||||
unsigned ns = res->num_states();
|
||||
for (unsigned s = 0; s < ns; ++s)
|
||||
{
|
||||
acc_cond::mark_t acc = accst[s];
|
||||
if (acc == 0U)
|
||||
continue;
|
||||
for (auto& t: res->out(s))
|
||||
t.acc = acc;
|
||||
}
|
||||
}
|
||||
|
||||
res->purge_unreachable_states();
|
||||
|
||||
delete gb;
|
||||
res->prop_copy(original_,
|
||||
{ false, // state-based acc forced below
|
||||
true, // weakness preserved,
|
||||
true, // determinism checked and overridden below
|
||||
// and "unambiguous" property preserved
|
||||
true, // stutter inv.
|
||||
});
|
||||
if (nb_minato == nb_satoneset && !Cosimulation)
|
||||
res->prop_deterministic(true);
|
||||
if (Sba)
|
||||
res->prop_state_acc(true);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// Debug:
|
||||
// In a first time, print the signature, and the print a list
|
||||
// of each state in this partition.
|
||||
// In a second time, print foreach state, who is where,
|
||||
// where is the new class name.
|
||||
void print_partition()
|
||||
{
|
||||
for (auto& p: bdd_lstate_)
|
||||
{
|
||||
std::cerr << "partition: "
|
||||
<< bdd_format_isop(a_->get_dict(), p.first)
|
||||
<< std::endl;
|
||||
for (auto s: p.second)
|
||||
std::cerr << " - "
|
||||
<< a_->format_state(a_->state_from_number(s))
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
std::cerr << "\nPrevious iteration\n" << std::endl;
|
||||
|
||||
unsigned ps = previous_class_.size();
|
||||
for (unsigned p = 0; p < ps; ++p)
|
||||
{
|
||||
std::cerr << a_->format_state(a_->state_from_number(p))
|
||||
<< " was in "
|
||||
<< bdd_format_set(a_->get_dict(), previous_class_[p])
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// The automaton which is simulated.
|
||||
twa_graph_ptr a_;
|
||||
|
||||
// Relation is aimed to represent the same thing than
|
||||
// rel_. The difference is in the way it does.
|
||||
// If A => A /\ A => B, rel will be (!A U B), but relation_
|
||||
// will have A /\ B at the key A. This trick is due to a problem
|
||||
// with the computation of the resulting automaton with the signature.
|
||||
// rel_ will pollute the meaning of the signature.
|
||||
map_bdd_bdd relation_;
|
||||
|
||||
// Represent the class of each state at the previous iteration.
|
||||
vector_state_bdd previous_class_;
|
||||
|
||||
// The list of state for each class at the current_iteration.
|
||||
// Computed in `update_sig'.
|
||||
map_bdd_lstate bdd_lstate_;
|
||||
|
||||
// The queue of free bdd. They will be used as the identifier
|
||||
// for the class.
|
||||
std::queue<int> free_var_;
|
||||
|
||||
// The list of used bdd. They are in used as identifier for class.
|
||||
std::list<bdd> used_var_;
|
||||
|
||||
// Size of the automaton.
|
||||
unsigned int size_a_;
|
||||
|
||||
// Used to know when there is no evolution in the po. Updated
|
||||
// in the `update_po' method.
|
||||
unsigned int po_size_;
|
||||
|
||||
// All the class variable:
|
||||
bdd all_class_var_;
|
||||
|
||||
// The flag to say if the outgoing state is initial or not
|
||||
bdd bdd_initial;
|
||||
|
||||
bdd all_proms_;
|
||||
|
||||
automaton_size stat;
|
||||
|
||||
std::unique_ptr<scc_info> scc_info_;
|
||||
|
||||
const const_twa_graph_ptr original_;
|
||||
};
|
||||
|
||||
} // End namespace anonymous.
|
||||
|
||||
|
||||
twa_graph_ptr
|
||||
simulation(const const_twa_graph_ptr& t)
|
||||
{
|
||||
direct_simulation<false, false> simul(t);
|
||||
return simul.run();
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
simulation_sba(const const_twa_graph_ptr& t)
|
||||
{
|
||||
direct_simulation<false, true> simul(t);
|
||||
return simul.run();
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
cosimulation(const const_twa_graph_ptr& t)
|
||||
{
|
||||
direct_simulation<true, false> simul(t);
|
||||
return simul.run();
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
cosimulation_sba(const const_twa_graph_ptr& t)
|
||||
{
|
||||
direct_simulation<true, true> simul(t);
|
||||
return simul.run();
|
||||
}
|
||||
|
||||
|
||||
template<bool Sba>
|
||||
twa_graph_ptr
|
||||
iterated_simulations_(const const_twa_graph_ptr& t)
|
||||
{
|
||||
twa_graph_ptr res = nullptr;
|
||||
automaton_size prev;
|
||||
automaton_size next;
|
||||
|
||||
do
|
||||
{
|
||||
prev = next;
|
||||
direct_simulation<false, Sba> simul(res ? res : t);
|
||||
res = simul.run();
|
||||
if (res->prop_deterministic())
|
||||
break;
|
||||
|
||||
direct_simulation<true, Sba> cosimul(res);
|
||||
res = cosimul.run();
|
||||
|
||||
if (Sba)
|
||||
res = scc_filter_states(res, false);
|
||||
else
|
||||
res = scc_filter(res, false);
|
||||
|
||||
next.set_size(res);
|
||||
}
|
||||
while (prev != next);
|
||||
return res;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
iterated_simulations(const const_twa_graph_ptr& t)
|
||||
{
|
||||
return iterated_simulations_<false>(t);
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
iterated_simulations_sba(const const_twa_graph_ptr& t)
|
||||
{
|
||||
return iterated_simulations_<true>(t);
|
||||
}
|
||||
|
||||
} // End namespace spot.
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 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 "misc/common.hh"
|
||||
#include "twa/twagraph.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \addtogroup twa_reduction
|
||||
/// @{
|
||||
|
||||
/// @{
|
||||
/// \brief Attempt to reduce the automaton by direct simulation.
|
||||
///
|
||||
/// When the suffixes (letter and acceptance conditions) reachable
|
||||
/// from one state are included in the suffixes seen by another one,
|
||||
/// the former state can be merged into the latter. The algorithm is
|
||||
/// based on the following paper, but generalized to handle TωA
|
||||
/// directly.
|
||||
///
|
||||
/** \verbatim
|
||||
@InProceedings{ etessami.00.concur,
|
||||
author = {Kousha Etessami and Gerard J. Holzmann},
|
||||
title = {Optimizing {B\"u}chi Automata},
|
||||
booktitle = {Proceedings of the 11th International Conference on
|
||||
Concurrency Theory (Concur'00)},
|
||||
pages = {153--167},
|
||||
year = {2000},
|
||||
editor = {C. Palamidessi},
|
||||
volume = {1877},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
address = {Pennsylvania, USA},
|
||||
publisher = {Springer-Verlag}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// Our reconstruction of the quotient automaton based on this
|
||||
/// suffix-inclusion relation will also improve determinism.
|
||||
///
|
||||
/// We recommend to call scc_filter() to first simplify the
|
||||
/// automaton that should be reduced by simulation.
|
||||
///
|
||||
/// Reducing an automaton by simulation does not change the number
|
||||
/// of acceptance conditions. In some rare cases (1 out of more
|
||||
/// than 500 in our benchmark), the reduced automaton will use more
|
||||
/// acceptance conditions than necessary, and running scc_filter()
|
||||
/// again afterwards will remove these superfluous conditions.
|
||||
///
|
||||
/// \param automaton the automaton to simulate.
|
||||
/// \return a new automaton which is at worst a copy of the received
|
||||
/// one
|
||||
SPOT_API twa_graph_ptr
|
||||
simulation(const const_twa_graph_ptr& automaton);
|
||||
SPOT_API twa_graph_ptr
|
||||
simulation_sba(const const_twa_graph_ptr& automaton);
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \brief Attempt to reduce the automaton by reverse simulation.
|
||||
///
|
||||
/// When the prefixes (letter and acceptance conditions) leading to
|
||||
/// one state are included in the prefixes leading to one, the former
|
||||
/// state can be merged into the latter.
|
||||
///
|
||||
/// Reverse simulation is discussed in the following paper,
|
||||
/// but generalized to handle TωA directly.
|
||||
/** \verbatim
|
||||
@InProceedings{ somenzi.00.cav,
|
||||
author = {Fabio Somenzi and Roderick Bloem},
|
||||
title = {Efficient {B\"u}chi Automata for {LTL} Formul{\ae}},
|
||||
booktitle = {Proceedings of the 12th International Conference on
|
||||
Computer Aided Verification (CAV'00)},
|
||||
pages = {247--263},
|
||||
year = {2000},
|
||||
volume = {1855},
|
||||
series = {Lecture Notes in Computer Science},
|
||||
address = {Chicago, Illinois, USA},
|
||||
publisher = {Springer-Verlag}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// Our reconstruction of the quotient automaton based on this
|
||||
/// prefix-inclusion relation will also improve codeterminism.
|
||||
///
|
||||
/// We recommend to call scc_filter() to first simplify the
|
||||
/// automaton that should be reduced by cosimulation.
|
||||
///
|
||||
/// Reducing an automaton by reverse simulation (1) does not change
|
||||
/// the number of acceptance conditions so the resulting automaton
|
||||
/// may have superfluous acceptance conditions, and (2) can create
|
||||
/// SCCs that are terminal and non-accepting. For these reasons,
|
||||
/// you should call scc_filer() to prune useless SCCs and acceptance
|
||||
/// conditions afterwards.
|
||||
///
|
||||
/// If you plan to run both simulation() and cosimulation() on the
|
||||
/// same automaton, you should start with simulation() so that the
|
||||
/// codeterminism improvements achieved by cosimulation() does not
|
||||
/// hinder the determinism improvements attempted by simulation().
|
||||
/// (This of course assumes that you prefer determinism over
|
||||
/// codeterminism.)
|
||||
///
|
||||
/// \param automaton the automaton to simulate.
|
||||
/// \return a new automaton which is at worst a copy of the received
|
||||
/// one
|
||||
SPOT_API twa_graph_ptr
|
||||
cosimulation(const const_twa_graph_ptr& automaton);
|
||||
SPOT_API twa_graph_ptr
|
||||
cosimulation_sba(const const_twa_graph_ptr& automaton);
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \brief Iterate simulation() and cosimulation().
|
||||
///
|
||||
/// Runs simulation(), cosimulation(), and scc_filter() in a loop,
|
||||
/// until the automaton does not change size (states and
|
||||
/// transitions).
|
||||
///
|
||||
/// We recommend to call scc_filter() to first simplify the
|
||||
/// automaton that should be reduced by iterated simulations, since
|
||||
/// this algorithm will only call scc_filter() at the end of the
|
||||
/// loop.
|
||||
///
|
||||
/// \param automaton the automaton to simulate.
|
||||
/// \return a new automaton which is at worst a copy of the received
|
||||
/// one
|
||||
SPOT_API twa_graph_ptr
|
||||
iterated_simulations(const const_twa_graph_ptr& automaton);
|
||||
SPOT_API twa_graph_ptr
|
||||
iterated_simulations_sba(const const_twa_graph_ptr& automaton);
|
||||
/// @}
|
||||
|
||||
} // End namespace spot.
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2011, 2012, 2013, 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 <sstream>
|
||||
#include "twa/twa.hh"
|
||||
#include "stats.hh"
|
||||
#include "reachiter.hh"
|
||||
#include "tl/print.hh"
|
||||
#include "twaalgos/isdet.hh"
|
||||
#include "twaalgos/sccinfo.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class stats_bfs: public tgba_reachable_iterator_breadth_first
|
||||
{
|
||||
public:
|
||||
stats_bfs(const const_twa_ptr& a, twa_statistics& s)
|
||||
: tgba_reachable_iterator_breadth_first(a), s_(s)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
process_state(const state*, int, twa_succ_iterator*)
|
||||
{
|
||||
++s_.states;
|
||||
}
|
||||
|
||||
void
|
||||
process_link(const state*, int, const state*, int,
|
||||
const twa_succ_iterator*)
|
||||
{
|
||||
++s_.edges;
|
||||
}
|
||||
|
||||
private:
|
||||
twa_statistics& s_;
|
||||
};
|
||||
|
||||
class sub_stats_bfs: public stats_bfs
|
||||
{
|
||||
public:
|
||||
sub_stats_bfs(const const_twa_ptr& a, twa_sub_statistics& s)
|
||||
: stats_bfs(a, s), s_(s), seen_(bddtrue)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
process_link(const state*, int, const state*, int,
|
||||
const twa_succ_iterator* it)
|
||||
{
|
||||
++s_.edges;
|
||||
|
||||
bdd cond = it->cond();
|
||||
bdd newvars = bdd_exist(bdd_support(cond), seen_);
|
||||
if (newvars != bddtrue)
|
||||
{
|
||||
seen_ &= newvars;
|
||||
int count = 0;
|
||||
while (newvars != bddtrue)
|
||||
{
|
||||
++count;
|
||||
newvars = bdd_high(newvars);
|
||||
}
|
||||
// If we discover one new variable, that means that all
|
||||
// transitions we counted so far are actually double
|
||||
// subtransitions. If we have two new variables, they where
|
||||
// quadruple transitions, etc.
|
||||
s_.transitions <<= count;
|
||||
}
|
||||
while (cond != bddfalse)
|
||||
{
|
||||
cond -= bdd_satoneset(cond, seen_, bddtrue);
|
||||
++s_.transitions;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
twa_sub_statistics& s_;
|
||||
bdd seen_;
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
|
||||
std::ostream& twa_statistics::dump(std::ostream& out) const
|
||||
{
|
||||
out << "edges: " << edges << '\n';
|
||||
out << "states: " << states << '\n';
|
||||
return out;
|
||||
}
|
||||
|
||||
std::ostream& twa_sub_statistics::dump(std::ostream& out) const
|
||||
{
|
||||
out << "transitions: " << transitions << '\n';
|
||||
this->twa_statistics::dump(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
twa_statistics
|
||||
stats_reachable(const const_twa_ptr& g)
|
||||
{
|
||||
twa_statistics s;
|
||||
stats_bfs d(g, s);
|
||||
d.run();
|
||||
return s;
|
||||
}
|
||||
|
||||
twa_sub_statistics
|
||||
sub_stats_reachable(const const_twa_ptr& g)
|
||||
{
|
||||
twa_sub_statistics s;
|
||||
sub_stats_bfs d(g, s);
|
||||
d.run();
|
||||
return s;
|
||||
}
|
||||
|
||||
void printable_formula::print(std::ostream& os, const char*) const
|
||||
{
|
||||
print_psl(os, val_);
|
||||
};
|
||||
|
||||
stat_printer::stat_printer(std::ostream& os, const char* format)
|
||||
: format_(format)
|
||||
{
|
||||
declare('a', &acc_);
|
||||
declare('c', &scc_);
|
||||
declare('d', &deterministic_);
|
||||
declare('e', &edges_);
|
||||
declare('f', &form_);
|
||||
declare('g', &gen_acc_);
|
||||
declare('n', &nondetstates_);
|
||||
declare('p', &complete_);
|
||||
declare('r', &run_time_);
|
||||
declare('s', &states_);
|
||||
declare('S', &scc_); // Historical. Deprecated. Use %c instead.
|
||||
declare('t', &trans_);
|
||||
set_output(os);
|
||||
if (format)
|
||||
prime(format);
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
stat_printer::print(const const_twa_graph_ptr& aut,
|
||||
formula f, double run_time)
|
||||
{
|
||||
form_ = f;
|
||||
run_time_ = run_time;
|
||||
|
||||
if (has('t'))
|
||||
{
|
||||
twa_sub_statistics s = sub_stats_reachable(aut);
|
||||
states_ = s.states;
|
||||
edges_ = s.edges;
|
||||
trans_ = s.transitions;
|
||||
}
|
||||
else if (has('s') || has('e'))
|
||||
{
|
||||
twa_sub_statistics s = sub_stats_reachable(aut);
|
||||
states_ = s.states;
|
||||
edges_ = s.edges;
|
||||
}
|
||||
|
||||
if (has('a'))
|
||||
acc_ = aut->num_sets();
|
||||
|
||||
if (has('c') || has('S'))
|
||||
scc_ = scc_info(aut).scc_count();
|
||||
|
||||
if (has('n'))
|
||||
{
|
||||
nondetstates_ = count_nondet_states(aut);
|
||||
deterministic_ = (nondetstates_ == 0);
|
||||
}
|
||||
else if (has('d'))
|
||||
{
|
||||
// This is more efficient than calling count_nondet_state().
|
||||
deterministic_ = is_deterministic(aut);
|
||||
}
|
||||
|
||||
if (has('p'))
|
||||
{
|
||||
complete_ = is_complete(aut);
|
||||
}
|
||||
|
||||
if (has('g'))
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << aut->get_acceptance();
|
||||
gen_acc_ = os.str();
|
||||
}
|
||||
|
||||
return format(format_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2008, 2011, 2012, 2013, 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "twa/twa.hh"
|
||||
#include <iosfwd>
|
||||
#include "misc/formater.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
|
||||
/// \addtogroup twa_misc
|
||||
/// @{
|
||||
|
||||
struct SPOT_API twa_statistics
|
||||
{
|
||||
unsigned edges;
|
||||
unsigned states;
|
||||
|
||||
twa_statistics() { edges = 0; states = 0; }
|
||||
std::ostream& dump(std::ostream& out) const;
|
||||
};
|
||||
|
||||
struct SPOT_API twa_sub_statistics: public twa_statistics
|
||||
{
|
||||
unsigned transitions;
|
||||
|
||||
twa_sub_statistics() { transitions = 0; }
|
||||
std::ostream& dump(std::ostream& out) const;
|
||||
};
|
||||
|
||||
/// \brief Compute statistics for an automaton.
|
||||
SPOT_API twa_statistics stats_reachable(const const_twa_ptr& g);
|
||||
/// \brief Compute sub statistics for an automaton.
|
||||
SPOT_API twa_sub_statistics sub_stats_reachable(const const_twa_ptr& g);
|
||||
|
||||
|
||||
class SPOT_API printable_formula: public printable_value<formula>
|
||||
{
|
||||
public:
|
||||
printable_formula&
|
||||
operator=(formula new_val)
|
||||
{
|
||||
val_ = new_val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual void
|
||||
print(std::ostream& os, const char*) const;
|
||||
};
|
||||
|
||||
/// \brief prints various statistics about a TGBA
|
||||
///
|
||||
/// This object can be configured to display various statistics
|
||||
/// about a TGBA. Some %-sequence of characters are interpreted in
|
||||
/// the format string, and replaced by the corresponding statistics.
|
||||
class SPOT_API stat_printer: protected formater
|
||||
{
|
||||
public:
|
||||
stat_printer(std::ostream& os, const char* format);
|
||||
|
||||
/// \brief print the configured statistics.
|
||||
///
|
||||
/// The \a f argument is not needed if the Formula does not need
|
||||
/// to be output, and so is \a run_time).
|
||||
std::ostream&
|
||||
print(const const_twa_graph_ptr& aut, formula f = nullptr,
|
||||
double run_time = -1.);
|
||||
|
||||
private:
|
||||
const char* format_;
|
||||
|
||||
printable_formula form_;
|
||||
printable_value<unsigned> states_;
|
||||
printable_value<unsigned> edges_;
|
||||
printable_value<unsigned> trans_;
|
||||
printable_value<unsigned> acc_;
|
||||
printable_value<unsigned> scc_;
|
||||
printable_value<unsigned> nondetstates_;
|
||||
printable_value<unsigned> deterministic_;
|
||||
printable_value<unsigned> complete_;
|
||||
printable_value<double> run_time_;
|
||||
printable_value<std::string> gen_acc_;
|
||||
};
|
||||
|
||||
/// @}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue