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:
Alexandre Duret-Lutz 2015-12-04 19:42:23 +01:00
parent 1fddfe60ec
commit f120dd3206
529 changed files with 1308 additions and 1262 deletions

View file

@ -1,6 +0,0 @@
.deps
.libs
*.lo
*.la
Makefile
Makefile.in

View file

@ -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

View file

@ -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_;
}
}

View file

@ -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;
};
}

View file

@ -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;
}
}

View file

@ -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.
};
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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()));
}
}

View file

@ -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);
/// @}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
};
}

View file

@ -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);
}
}

View file

@ -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);
/// \@}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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
}

View file

@ -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;
};
/// @}
}

View file

@ -1,6 +0,0 @@
.deps
.libs
*.lo
*.la
Makefile
Makefile.in

View file

@ -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

View file

@ -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;
}
}

View file

@ -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_;
};
}

View file

@ -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);
}
}

View file

@ -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>&lt;Sigma, Q, delta, q, F&gt;</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
/// @}
}

View file

@ -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;
}
}

View file

@ -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;
};
}

View file

@ -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();
}
}

View file

@ -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;
};
}

View file

@ -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);
}
}

View file

@ -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());
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
/// @}
}

View file

@ -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();
}
}

View file

@ -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);
/// @}
}

View file

@ -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));
}
}

View file

@ -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);
/// @}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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());
/// @}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
/// @}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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_;
};
/// @}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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());
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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_();
}
}

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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_;
};
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
};
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
/// @}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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.

View file

@ -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.

View file

@ -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_);
}
}

View file

@ -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