rename src/tgbaalgos/ as src/twaalgos/

Automatic mass renaming.

* src/tgbaalgos/: Rename as...
* src/twaalgos/: ... this.
* README, configure.ac, iface/ltsmin/modelcheck.cc, src/Makefile.am,
src/bin/autfilt.cc, src/bin/common_aoutput.cc,
src/bin/common_aoutput.hh, src/bin/common_output.hh,
src/bin/common_post.hh, src/bin/dstar2tgba.cc, src/bin/ltl2tgba.cc,
src/bin/ltl2tgta.cc, src/bin/ltlcross.cc, src/bin/ltldo.cc,
src/bin/ltlfilt.cc, src/bin/randaut.cc, src/dstarparse/dra2ba.cc,
src/dstarparse/nra2nba.cc, src/dstarparse/nsa2tgba.cc,
src/graphtest/twagraph.cc, src/kripke/kripkeprint.cc,
src/ltlvisit/contain.cc, src/ltlvisit/contain.hh,
src/ltlvisit/exclusive.cc, src/taalgos/emptinessta.hh,
src/tgbatest/checkpsl.cc, src/tgbatest/checkta.cc,
src/tgbatest/complementation.cc, src/tgbatest/emptchk.cc,
src/tgbatest/ltl2tgba.cc, src/tgbatest/ltlprod.cc,
src/tgbatest/randtgba.cc, src/tgbatest/taatgba.cc, src/twa/twa.cc,
src/twa/twagraph.hh, src/twa/twasafracomplement.cc,
wrap/python/spot_impl.i: Adjust.
This commit is contained in:
Alexandre Duret-Lutz 2015-04-22 17:52:27 +02:00
parent 703fbd0e99
commit de529df59f
159 changed files with 260 additions and 272 deletions

6
src/twaalgos/.gitignore vendored Normal file
View file

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

143
src/twaalgos/Makefile.am Normal file
View file

@ -0,0 +1,143 @@
## -*- 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 \
compsusp.hh \
cycles.hh \
dtgbacomp.hh \
degen.hh \
dotty.hh \
dtbasat.hh \
dtgbasat.hh \
dupexp.hh \
emptiness.hh \
emptiness_stats.hh \
gv04.hh \
hoa.hh \
isdet.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 \
reducerun.hh \
relabel.hh \
remfin.hh \
remprop.hh \
replayrun.hh \
safety.hh \
sbacc.hh \
sccfilter.hh \
scc.hh \
sccinfo.hh \
se05.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 \
compsusp.cc \
cycles.cc \
dtgbacomp.cc \
degen.cc \
dotty.cc \
dtbasat.cc \
dtgbasat.cc \
dupexp.cc \
emptiness.cc \
gv04.cc \
hoa.cc \
isdet.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 \
reducerun.cc \
remfin.cc \
remprop.cc \
replayrun.cc \
relabel.cc \
safety.cc \
sbacc.cc \
scc.cc \
sccinfo.cc \
sccfilter.cc \
se05.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

@ -0,0 +1,151 @@
// -*- 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::trans_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_transitions() != aut2->num_transitions() ||
// 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_->is_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->is_deterministic() || spot::is_deterministic(aut))
{
return are_isomorphic_det(ref_, aut);
}
}
else
{
if (aut->is_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

@ -0,0 +1,54 @@
// -*- coding: utf-8 -*-
// Copyright (C) 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 "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;
std::vector<twa_graph::graph_t::trans_storage_t> reftrans_;
};
}

108
src/twaalgos/bfssteps.cc Normal file
View file

@ -0,0 +1,108 @@
// -*- 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*, tgba_run::step,
state_ptr_less_than>& father, const tgba_run::step& s,
const state* start, tgba_run::steps& l)
{
tgba_run::steps p;
tgba_run::step current = s;
for (;;)
{
tgba_run::step tmp = current;
tmp.s = tmp.s->clone();
p.push_front(tmp);
if (current.s == start)
break;
std::map<const state*, tgba_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, tgba_run::steps& l)
{
// Records backlinks to parent state during the BFS.
// (This also stores the propositions of this link.)
std::map<const state*, tgba_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->current_state());
if (!dest)
continue;
bdd cond = i->current_condition();
acc_cond::mark_t acc = i->current_acceptance_conditions();
tgba_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 0;
}
}

102
src/twaalgos/bfssteps.hh Normal file
View file

@ -0,0 +1,102 @@
// -*- 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 tgba_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, tgba_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::current_state()), 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(tgba_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*, tgba_run::step,
state_ptr_less_than>& father,
const tgba_run::step& s,
const state* start,
tgba_run::steps& l);
protected:
const_twa_ptr a_; ///< The spot::tgba we are searching into.
};
}

View file

@ -0,0 +1,109 @@
// -*- 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/>.
#include "canonicalize.hh"
#include "twa/twagraph.hh"
#include <set>
#include <vector>
namespace
{
typedef std::pair<spot::twa_graph::graph_t::trans_data_t, unsigned>
trans_sig_t;
struct signature_t
{
std::vector<trans_sig_t> ingoing;
std::vector<trans_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->transitions())
{
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_transitions_();
g.chain_transitions_();
return aut;
}
}

View file

@ -0,0 +1,30 @@
// -*- 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);
}

63
src/twaalgos/cleanacc.cc Normal file
View file

@ -0,0 +1,63 @@
// -*- 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->transitions())
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->transitions())
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()));
}
}

34
src/twaalgos/cleanacc.hh Normal file
View file

@ -0,0 +1,34 @@
// -*- 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);
/// @}
}

132
src/twaalgos/complete.cc Normal file
View file

@ -0,0 +1,132 @@
// -*- 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 tgba_complete_here(twa_graph_ptr aut)
{
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->transition_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_transitions();
// 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 transition in the same set as all
// the other.
//
// In case the automaton uses transition-based acceptance,
// it does not matter what acceptance set we put the new
// transition into.
//
// So in both cases, we put the transition in the same
// acceptance sets as the last outgoing transition of the
// state.
acc = t.acc;
}
// If the state has incomplete successors, we need to add a
// transition 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_transition(sink, sink, bddtrue, um.second);
}
// In case the automaton use state-based acceptance, propagate
// the acceptance of the first transition to the one we add.
aut->new_transition(i, sink, missingcond, acc);
}
}
// Get rid of any named property if the automaton changed.
if (t < aut->num_transitions())
aut->release_named_properties();
else
assert(t == aut->num_transitions());
return sink;
}
twa_graph_ptr tgba_complete(const const_twa_ptr& aut)
{
auto res = make_twa_graph(aut, {
true, // state based
true, // inherently_weak
true, // deterministic
true, // stutter inv.
});
tgba_complete_here(res);
return res;
}
}

39
src/twaalgos/complete.hh Normal file
View file

@ -0,0 +1,39 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2013, 2014 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 tgba 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 not outgoing
/// transitions apart from self-loops.)
SPOT_API unsigned tgba_complete_here(twa_graph_ptr aut);
/// \brief Clone a tgba and complete it.
///
/// If the tgba has no acceptance set, one will be added.
SPOT_API twa_graph_ptr tgba_complete(const const_twa_ptr& aut);
}

407
src/twaalgos/compsusp.cc Normal file
View file

@ -0,0 +1,407 @@
// -*- 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/>.
#include "compsusp.hh"
#include "sccfilter.hh"
#include "sccinfo.hh"
#include "twa/twagraph.hh"
#include "ltl2tgba_fm.hh"
#include "minimize.hh"
#include "simulation.hh"
#include "safety.hh"
#include "ltlast/allnodes.hh"
#include "ltlvisit/tostring.hh"
#include "ltlvisit/clone.hh"
#include <queue>
#include <sstream>
#include "ltlenv/environment.hh"
namespace spot
{
namespace
{
typedef std::map<const ltl::formula*, bdd> formula_bdd_map;
// An environment to store atomic proposition associated to
// suspended variable. (We don't use the default environment to
// avoid conflicts with user-defined atomic propositions that
// would share the same name.)
class suspended_environment: public ltl::environment
{
public:
const ltl::formula*
require(const std::string& s)
{
return ltl::atomic_prop::instance(s, *this);
}
const std::string&
name() const
{
static std::string name("suspended environment");
return name;
}
};
static suspended_environment suspenv;
// 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: public ltl::clone_visitor
{
public:
typedef std::map<const ltl::formula*, const ltl::formula*> fmap_t;
ltl_suspender_visitor(fmap_t& g2s, fmap_t& a2o, bool oblig)
: g2s_(g2s), a2o_(a2o), oblig_(oblig)
{
}
void
visit(const ltl::multop* mo)
{
ltl::multop::type op = mo->op();
switch (op)
{
case ltl::multop::Or:
case ltl::multop::And:
{
ltl::multop::vec* res = new ltl::multop::vec;
ltl::multop::vec* oblig = oblig_ ? new ltl::multop::vec : 0;
ltl::multop::vec* susp = new ltl::multop::vec;
unsigned mos = mo->size();
for (unsigned i = 0; i < mos; ++i)
{
const ltl::formula* c = mo->nth(i);
if (c->is_boolean())
res->push_back(c->clone());
else if (oblig_ && c->is_syntactic_obligation())
oblig->push_back(c->clone());
else if (c->is_eventual() && c->is_universal())
susp->push_back(c->clone());
else
res->push_back(recurse(c));
}
if (!oblig_ || oblig->empty())
{
delete oblig;
}
else
{
const ltl::formula* o = ltl::multop::instance(op, oblig);
res->push_back(recurse(o));
o->destroy();
}
if (susp->empty())
{
delete susp;
}
else
{
const ltl::formula* o = ltl::multop::instance(op, susp);
// Rewrite 'o' as 'G"o"'
const ltl::formula* g = recurse(o);
o->destroy();
if (op == ltl::multop::And)
{
res->push_back(g);
}
else
{
// res || susp -> (res && G![susp]) || G[susp])
const ltl::formula* r = ltl::multop::instance(op, res);
const ltl::unop* u =
down_cast<const ltl::unop*>(g);
const ltl::formula* gn =
ltl::unop::instance
(ltl::unop::G, ltl::unop::instance
(ltl::unop::Not, u->child()->clone()));
result_ = ltl::multop::instance
(ltl::multop::Or, ltl::multop::instance
(ltl::multop::And, r, gn),
g);
return;
}
}
result_ = ltl::multop::instance(op, res);
}
break;
case ltl::multop::OrRat:
case ltl::multop::AndRat:
case ltl::multop::AndNLM:
case ltl::multop::Concat:
case ltl::multop::Fusion:
this->ltl::clone_visitor::visit(mo);
break;
}
}
const ltl::formula*
recurse(const ltl::formula* f)
{
const ltl::formula* res;
if (f->is_boolean())
return f->clone();
if (oblig_ && f->is_syntactic_obligation())
{
fmap_t::const_iterator i = assoc_.find(f);
if (i != assoc_.end())
return i->second->clone();
std::ostringstream s;
s << "";
to_string(f, s);
s << "";
res = suspenv.require(s.str());
// We have to clone f, because it is not always a sub-tree
// of the original formula. (Think n-ary operators.)
a2o_[res] = f->clone();
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 ltl::unop::instance(ltl::unop::G, i->second->clone());
std::ostringstream s;
s << '[';
to_string(f, s);
s << ']';
res = suspenv.require(s.str());
// We have to clone f, because it is not always a sub-tree
// of the original formula. (Think n-ary operators.)
g2s_[res] = f->clone();
assoc_[f] = res;
return ltl::unop::instance(ltl::unop::G, res);
}
f->accept(*this);
return result_;
}
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, const ltl::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(), 0);
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->current_state(), ris);
bdd lc = li->current_condition();
twa_succ_iterator* ri = 0;
// 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 transitions
// 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->current_condition();
// Skip incompatible transitions.
if (cond == bddfalse)
{
ri->next();
continue;
}
d.second = ri->current_state();
racc = ri->current_acceptance_conditions();
}
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->current_acceptance_conditions(),
ra, racc);
res->new_transition(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(const ltl::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);
const ltl::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, 0, 0),
false);
if (!no_wdba)
{
twa_graph_ptr min = minimize_obligation(res, g, 0, 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);
g->destroy();
for (ltl_suspender_visitor::fmap_t::iterator i = g2s.begin();
i != g2s.end(); ++i)
i->second->destroy();
for (ltl_suspender_visitor::fmap_t::iterator i = a2o.begin();
i != a2o.end(); ++i)
i->second->destroy();
return res;
}
}

56
src/twaalgos/compsusp.hh Normal file
View file

@ -0,0 +1,56 @@
// -*- 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 "ltlast/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(const ltl::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);
}

162
src/twaalgos/cycles.cc Normal file
View file

@ -0,0 +1,162 @@
// -*- 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_->trans_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_->trans_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;
}
}

164
src/twaalgos/cycles.hh Normal file
View file

@ -0,0 +1,164 @@
// -*- 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);
};
}

581
src/twaalgos/degen.cc Normal file
View file

@ -0,0 +1,581 @@
// -*- 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 transitions (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 transitions 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_based_acc();
// 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->acc().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 transitions 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 transition 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 transitions, 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
// transitions (of the current state) AND if it is not
// used by any outgoing transition of the destination
// state.
//
// 1) It's correct to do that, because this acceptance
// set is common to other outgoing transitions.
// 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
// transitions: 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 transition in the SLEVEL acceptance set should
// be directed to the next acceptance set. If the
// current transition 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 transitions 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
// swhitching 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 transitions 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 transition, and
// we do not need to stay use final level.
if (!want_sba)
{
is_acc = d.second == order.size();
if (is_acc) // The transition 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 transition.
t = res->new_acc_transition(src, dest, i.cond, is_acc);
else // Update existing transition.
res->trans_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_transitions();
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);
}
}

66
src/twaalgos/degen.hh Normal file
View file

@ -0,0 +1,66 @@
// -*- 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 accepting 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);
/// \@}
}

503
src/twaalgos/dotty.cc Normal file
View file

@ -0,0 +1,503 @@
// -*- 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 "dotty.hh"
#include "twa/bddprint.hh"
#include "reachiter.hh"
#include "misc/escape.hh"
#include "twa/twagraph.hh"
#include "twa/formula2bdd.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;
bool opt_circles_ = false;
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_;
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_all_bullets = false;
bool opt_numbered_trans = false;
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 'a':
opt_show_acc_ = true;
break;
case 'b':
opt_bullet = true;
break;
case 'c':
opt_circles_ = true;
break;
case 'h':
opt_horizontal_ = true;
break;
case 'f':
if (*options != '(')
throw std::runtime_error
(std::string("invalid font specification for dotty()"));
{
auto* end = strchr(++options, ')');
if (!end)
throw std::runtime_error
(std::string("invalid font specification for dotty()"));
opt_font_ = std::string(options, end - options);
options = end + 1;
}
break;
case '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 dotty(): ") + c);
}
}
dotty_output(std::ostream& os, const char* options)
: os_(os)
{
parse_opts(options ? options : ".");
}
void
output_set(std::ostream& os, int v) const
{
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 % 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_->acc().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";
}
if (opt_circles_)
os_ << " node [shape=\"circle\"]\n";
if (!opt_font_.empty())
os_ << " fontname=\"" << opt_font_
<< "\"\n node [fontname=\"" << opt_font_
<< "\"]\n edge [fontname=\"" << opt_font_
<< "\"]\n";
// Always copy the environment variable into a static string,
// so that we (1) look it up once, but (2) won't crash if the
// environment is changed.
static std::string extra = []()
{
auto s = getenv("SPOT_DOTEXTRA");
return s ? s : "";
}();
// Any extra text passed in the SPOT_DOTEXTRA environment
// variable should be output at the end of the "header", so
// that our setup can be overridden.
if (!extra.empty())
os_ << " " << extra << '\n';
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 || aut_->acc().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
os_ << s;
if (acc)
{
os_ << "\\n";
output_set(acc);
}
os_ << '"';
}
else
{
os_ << '<';
if (has_name)
escape_html(os_, (*sn_)[s]);
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
os_ << s;
os_ << '"';
if (mark_states_ && aut_->state_is_accepting(s))
os_ << ", peripheries=2";
os_ << "]\n";
}
}
void
process_link(const twa_graph::trans_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;
sn_ = aut->get_named_prop<std::vector<std::string>>("state-names");
if (opt_name_)
name_ = aut_->get_named_prop<std::string>("automaton-name");
mark_states_ = !opt_force_acc_trans_ && aut_->has_state_based_acc();
auto si =
std::unique_ptr<scc_info>(opt_scc_ ? new scc_info(aut) : nullptr);
start();
if (si)
{
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
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&
dotty_reachable(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 = make_twa_graph(g, twa::prop_set::all());
d.print(aut);
return os;
}
}

47
src/twaalgos/dotty.hh Normal file
View file

@ -0,0 +1,47 @@
// -*- 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&
dotty_reachable(std::ostream& os,
const const_twa_ptr& g,
const char* options = nullptr);
}

851
src/twaalgos/dtbasat.cc Normal file
View file

@ -0,0 +1,851 @@
// -*- 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 "dotty.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->transitions())
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_based_acc();
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_transition(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->trans_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_transitions();
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)
{
tgba_sub_statistics st = sub_stats_reachable(res);
out << st.states << ',' << st.transitions
<< ',' << st.sub_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)
dotty_reachable(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 n_states = stats_reachable(a).states;
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 = 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;
}
}

66
src/twaalgos/dtbasat.hh Normal file
View file

@ -0,0 +1,66 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2013, 2014 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 TGBA. 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);
/// \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);
}

182
src/twaalgos/dtgbacomp.cc Normal file
View file

@ -0,0 +1,182 @@
// -*- 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 "dtgbacomp.hh"
#include "sccinfo.hh"
#include "complete.hh"
#include "cleanacc.hh"
namespace spot
{
twa_graph_ptr dtgba_complement_nonweak(const const_twa_graph_ptr& aut)
{
// Clone the original automaton.
auto res = make_twa_graph(aut,
{ false, // state based
false, // inherently_weak
false, // deterministic
true, // stutter inv.
});
// Copy the old acceptance condition before we replace it.
acc_cond oldacc = aut->acc(); // Copy it!
// We will modify res in place, and the resulting
// automaton will only have one acceptance set.
// This changes aut->acc();
res->set_buchi();
// The resulting automaton is weak.
res->prop_inherently_weak();
res->prop_state_based_acc();
unsigned num_sets = oldacc.num_sets();
unsigned n = res->num_states();
// We will duplicate the automaton as many times as we have
// acceptance sets, and we need one extra sink state.
res->new_states(num_sets * n + 1);
unsigned sink = res->num_states() - 1;
// The sink state has an accepting self-loop.
res->new_acc_transition(sink, sink, bddtrue);
for (unsigned src = 0; src < n; ++src)
{
// Keep track of all conditions on transition leaving state
// SRC, so we can complete it.
bdd missingcond = bddtrue;
for (auto& t: res->out(src))
{
if (t.dst >= n) // Ignore transitions we added.
break;
missingcond -= t.cond;
acc_cond::mark_t curacc = t.acc;
// The original transition must not accept anymore.
t.acc = 0U;
// Transition that were fully accepting are never cloned.
if (oldacc.accepting(curacc))
continue;
// Save t.cond and t.dst as the reference to t
// is invalided by calls to new_transition().
unsigned dst = t.dst;
bdd cond = t.cond;
// Iterate over all the acceptance conditions in 'curacc',
// an duplicate it for each clone for which it does not
// belong to the acceptance set.
unsigned add = 0;
for (unsigned set = 0; set < num_sets; ++set)
{
add += n;
if (!oldacc.has(curacc, set))
{
// Clone the transition
res->new_acc_transition(src + add, dst + add, cond);
assert(dst + add < sink);
// Using `t' is disallowed from now on as it is a
// reference to a transition that may have been
// reallocated.
// At least one transition per cycle should have a
// nondeterministic copy from the original clone.
// We use state numbers to select it, as any cycle
// is guaranteed to have at least one transition
// with dst <= src. FIXME: Computing a feedback
// arc set would be better.
if (dst <= src)
res->new_transition(src, dst + add, cond);
}
}
assert(add == num_sets * n);
}
// Complete the original automaton.
if (missingcond != bddfalse)
res->new_transition(src, sink, missingcond);
}
res->merge_transitions();
res->purge_dead_states();
return res;
}
twa_graph_ptr dtgba_complement_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.
});
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_based_acc();
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_rejecting_scc(scc) && !si.is_trivial(scc))
acc = all_acc;
// Keep track of all conditions on transition 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_transition(sink, sink, bddtrue);
}
res->new_transition(src, sink, missingcond);
}
}
//res->merge_transitions();
return res;
}
twa_graph_ptr dtgba_complement(const const_twa_graph_ptr& aut)
{
if (aut->acc().is_generalized_buchi())
{
if (aut->is_inherently_weak())
return dtgba_complement_weak(aut);
else
return dtgba_complement_nonweak(aut);
}
else
{
// Simply complete the automaton, and complement its
// acceptance.
auto res = cleanup_acceptance_here(tgba_complete(aut));
res->set_acceptance(res->acc().num_sets(),
res->get_acceptance().complement());
return res;
}
}
}

35
src/twaalgos/dtgbacomp.hh Normal file
View file

@ -0,0 +1,35 @@
// -*- 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 TGBA
///
/// The automaton \a aut should be deterministic. It does no need
/// to be complete. Acceptance can be transition-based, or
/// state-based. Unless the input automaton is marked as weak (in
/// which case the output will also be weak and deterministic) the
/// resulting automaton is very unlikely to be deterministic.
SPOT_API twa_graph_ptr
dtgba_complement(const const_twa_graph_ptr& aut);
}

998
src/twaalgos/dtgbasat.cc Normal file
View file

@ -0,0 +1,998 @@
// -*- 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 "dtgbasat.hh"
#include "reachiter.hh"
#include <map>
#include <utility>
#include "sccinfo.hh"
#include "twa/bddprint.hh"
#include "ltlast/constant.hh"
#include "stats.hh"
#include "ltlenv/defaultenv.hh"
#include "misc/satsolver.hh"
#include "misc/timer.hh"
#include "isweakscc.hh"
#include "dotty.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 (dtgba-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 = 0;
static const acc_cond* debug_ref_acc = 0;
static const acc_cond* debug_cand_acc = 0;
struct transition
{
unsigned src;
bdd cond;
unsigned dst;
transition(int src, bdd cond, int 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(int 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 transition_acc
{
unsigned src;
bdd cond;
acc_cond::mark_t acc;
unsigned dst;
transition_acc(int src, bdd cond, acc_cond::mark_t acc, int dst)
: src(src), cond(cond), acc(acc), dst(dst)
{
}
bool operator<(const transition_acc& 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;
if (this->cond.id() < other.cond.id())
return true;
if (this->cond.id() > other.cond.id())
return false;
return this->acc < other.acc;
}
bool operator==(const transition_acc& other) const
{
return (this->src == other.src
&& this->dst == other.dst
&& this->cond.id() == other.cond.id()
&& this->acc == other.acc);
}
};
struct path
{
unsigned src_cand;
unsigned src_ref;
unsigned dst_cand;
unsigned dst_ref;
acc_cond::mark_t acc_cand;
acc_cond::mark_t acc_ref;
path(unsigned src_cand, unsigned src_ref)
: src_cand(src_cand), src_ref(src_ref),
dst_cand(src_cand), dst_ref(src_ref),
acc_cand(0U), acc_ref(0U)
{
}
path(unsigned src_cand, unsigned src_ref,
unsigned dst_cand, unsigned dst_ref,
acc_cond::mark_t acc_cand, acc_cond::mark_t acc_ref)
: src_cand(src_cand), src_ref(src_ref),
dst_cand(dst_cand), dst_ref(dst_ref),
acc_cand(acc_cand), acc_ref(acc_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;
if (this->acc_ref < other.acc_ref)
return true;
if (this->acc_ref > other.acc_ref)
return false;
if (this->acc_cand < other.acc_cand)
return true;
if (this->acc_cand > other.acc_cand)
return false;
return false;
}
};
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 transition_acc& t)
{
os << '<' << t.src << ','
<< bdd_format_formula(debug_dict, t.cond) << ','
<< debug_cand_acc->format(t.acc)
<< ',' << 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 << ", "
<< debug_cand_acc->format(p.acc_cand) << ", "
<< debug_ref_acc->format(p.acc_ref) << '>';
return os;
}
struct dict
{
dict(const const_twa_ptr& a)
: aut(a)
{
}
const_twa_ptr aut;
typedef std::map<transition, int> trans_map;
typedef std::map<transition_acc, int> trans_acc_map;
trans_map transid;
trans_acc_map transaccid;
typedef std::map<int, transition> rev_map;
typedef std::map<int, transition_acc> rev_acc_map;
rev_map revtransid;
rev_acc_map revtransaccid;
std::map<path, int> pathid;
int nvars = 0;
//typedef std::unordered_map<const state*, int,
//state_ptr_hash, state_ptr_equal> state_map;
//typedef std::unordered_map<int, const state*> int_map;
//state_map state_to_int;
// int_map int_to_state;
unsigned cand_size;
unsigned int cand_nacc;
std::vector<acc_cond::mark_t> cand_acc; // size cand_nacc
std::vector<acc_cond::mark_t> all_cand_acc;
std::vector<acc_cond::mark_t> all_ref_acc;
std::vector<bool> is_weak_scc;
acc_cond cacc;
~dict()
{
aut->get_dict()->unregister_all_my_variables(this);
}
};
unsigned declare_vars(const const_twa_graph_ptr& aut,
dict& d, bdd ap, bool state_based, scc_info& sm)
{
bdd_dict_ptr bd = aut->get_dict();
d.cand_acc.resize(d.cand_nacc);
d.cacc.add_sets(d.cand_nacc);
d.all_cand_acc.push_back(0U);
for (unsigned n = 0; n < d.cand_nacc; ++n)
{
auto c = d.cacc.mark(n);
d.cand_acc[n] = c;
size_t s = d.all_cand_acc.size();
for (size_t i = 0; i < s; ++i)
d.all_cand_acc.push_back(d.all_cand_acc[i] | c);
}
d.all_ref_acc.push_back(0U);
unsigned ref_nacc = aut->acc().num_sets();
for (unsigned n = 0; n < ref_nacc; ++n)
{
auto c = aut->acc().mark(n);
size_t s = d.all_ref_acc.size();
for (size_t i = 0; i < s; ++i)
d.all_ref_acc.push_back(d.all_ref_acc[i] | c);
}
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_weak = d.is_weak_scc[i_scc];
for (unsigned j = 0; j < d.cand_size; ++j)
{
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)
{
size_t sfp = is_weak ? 1 : d.all_ref_acc.size();
for (size_t fp = 0; fp < sfp; ++fp)
{
size_t sf = d.all_cand_acc.size();
for (size_t f = 0; f < sf; ++f)
{
path p(j, i, l, k,
d.all_cand_acc[f],
d.all_ref_acc[fp]);
d.pathid[p] = ++d.nvars;
}
}
}
}
}
}
if (!state_based)
{
for (unsigned i = 0; i < d.cand_size; ++i)
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);
// Create the variable for the accepting transition
// immediately afterwards. It helps parsing the
// result.
for (unsigned n = 0; n < d.cand_nacc; ++n)
{
transition_acc ta(i, one, d.cand_acc[n], j);
d.transaccid[ta] = ++d.nvars;
d.revtransaccid.emplace(d.nvars, ta);
}
}
}
}
else // state based
{
for (unsigned i = 0; i < d.cand_size; ++i)
for (unsigned n = 0; n < d.cand_nacc; ++n)
{
++d.nvars;
for (unsigned j = 1; j < d.cand_size; ++j)
{
bdd all = bddtrue;
while (all != bddfalse)
{
bdd one = bdd_satoneset(all, ap, bddfalse);
all -= one;
transition_acc ta(i, one, d.cand_acc[n], j);
d.transaccid[ta] = d.nvars;
d.revtransaccid.emplace(d.nvars, ta);
}
}
}
for (unsigned i = 0; i < d.cand_size; ++i)
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);
}
}
}
return ref_size;
}
typedef std::pair<int, int> sat_stats;
static
sat_stats dtgba_to_sat(std::ostream& out, 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->transitions())
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);
d.is_weak_scc = sm.weak_sccs();
// 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();
debug_ref_acc = &ref->acc();
debug_cand_acc = &d.cacc;
dout << "ref_size: " << ref_size << '\n';
dout << "cand_size: " << d.cand_size << '\n';
#endif
auto& racc = ref->acc();
dout << "symmetry-breaking clauses\n";
int 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 << "(8) 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 << "(9) the initial state is reachable\n";
{
unsigned init = ref->get_init_state_number();
dout << path(0, init) << '\n';
out << d.pathid[path(0, init)] << " 0\n";
++nclauses;
}
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
for (unsigned q1p = 0; q1p < ref_size; ++q1p)
{
if (!sm.reachable_state(q1p))
continue;
dout << "(10) augmenting paths based on Cand[" << q1
<< "] and Ref[" << q1p << "]\n";
path p1(q1, q1p);
int p1id = d.pathid[p1];
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];
path p2(q2, dp);
int succ = d.pathid[p2];
if (p1id == succ)
continue;
dout << p1 << "" << t << "δ → " << p2 << '\n';
out << -p1id << ' ' << -ti << ' ' << succ << " 0\n";
++nclauses;
}
}
}
}
// construction of constraints (11,12,13)
for (unsigned q1p = 0; q1p < ref_size; ++q1p)
{
if (!sm.reachable_state(q1p))
continue;
unsigned q1p_scc = sm.scc_of(q1p);
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;
bool is_weak = d.is_weak_scc[q1p_scc];
bool is_acc = sm.is_accepting_scc(q1p_scc);
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
for (unsigned q2 = 0; q2 < d.cand_size; ++q2)
{
size_t sf = d.all_cand_acc.size();
size_t sfp = is_weak ? 1 : d.all_ref_acc.size();
for (size_t f = 0; f < sf; ++f)
for (size_t fp = 0; fp < sfp; ++fp)
{
path p(q1, q1p, q2, q2p,
d.all_cand_acc[f], d.all_ref_acc[fp]);
dout << "(11&12&13) paths from " << p << '\n';
int pid = d.pathid[p];
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)
{
bdd all = tr.cond;
acc_cond::mark_t curacc = tr.acc;
while (all != bddfalse)
{
bdd l = bdd_satoneset(all, ap, bddfalse);
all -= l;
transition t(q2, l, q3);
int ti = d.transid[t];
if (dp == q1p && q3 == q1) // (11,12) loop
{
if ((!is_acc) ||
(!is_weak &&
!racc.accepting
(curacc | d.all_ref_acc[fp])))
{
#if DEBUG
dout << "(11) " << p << ""
<< t << "δ → ¬(";
bool notfirst = false;
acc_cond::mark_t all_ =
d.all_cand_acc.back() -
d.all_cand_acc[f];
for (auto m: d.cacc.sets(all_))
{
transition_acc
ta(q2, l,
d.cacc.mark(m), q1);
if (notfirst)
out << "";
else
notfirst = true;
out << ta << "FC";
}
out << ")\n";
#endif // DEBUG
out << -pid << ' ' << -ti;
// 11
acc_cond::mark_t all_f =
d.all_cand_acc.back() -
d.all_cand_acc[f];
for (auto m: d.cacc.sets(all_f))
{
transition_acc
ta(q2, l,
d.cacc.mark(m), q1);
int tai = d.transaccid[ta];
assert(tai != 0);
out << ' ' << -tai;
}
out << " 0\n";
++nclauses;
}
else
{
#if DEBUG
dout << "(12) " << p << ""
<< t << "δ → (";
bool notfirst = false;
// 11
acc_cond::mark_t all_ =
d.cacc.comp(d.all_cand_acc[f]);
for (auto m: d.cacc.sets(all_))
{
transition_acc
ta(q2, l,
d.cacc.mark(m), q1);
if (notfirst)
out << "";
else
notfirst = true;
out << ta << "FC";
}
out << ")\n";
#endif // DEBUG
// 12
acc_cond::mark_t all_f =
d.cacc.comp(d.all_cand_acc[f]);
for (auto m: d.cacc.sets(all_f))
{
transition_acc
ta(q2, l,
d.cacc.mark(m), q1);
int tai = d.transaccid[ta];
assert(tai != 0);
out << -pid << ' ' << -ti
<< ' ' << tai << " 0\n";
++nclauses;
}
}
}
// (13) augmenting paths (always).
{
size_t sf = d.all_cand_acc.size();
for (size_t f = 0; f < sf; ++f)
{
acc_cond::mark_t f2 =
p.acc_cand | d.all_cand_acc[f];
acc_cond::mark_t f2p = 0U;
if (!is_weak)
f2p = p.acc_ref | curacc;
path p2(p.src_cand, p.src_ref,
q3, dp, f2, f2p);
int p2id = d.pathid[p2];
if (pid == p2id)
continue;
#if DEBUG
dout << "(13) " << p << ""
<< t << "δ ";
auto biga_ = d.all_cand_acc[f];
for (unsigned m = 0;
m < d.cand_nacc; ++m)
{
transition_acc
ta(q2, l,
d.cacc.mark(m), q3);
const char* not_ = "¬";
if (d.cacc.has(biga_, m))
not_ = "";
out << "" << not_
<< ta << "FC";
}
out << "" << p2 << '\n';
#endif
out << -pid << ' ' << -ti << ' ';
auto biga = d.all_cand_acc[f];
for (unsigned m = 0;
m < d.cand_nacc; ++m)
{
transition_acc
ta(q2, l,
d.cacc.mark(m), q3);
int tai = d.transaccid[ta];
if (d.cacc.has(biga, m))
tai = -tai;
out << tai << ' ';
}
out << p2id << " 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);
a->set_generalized_buchi(satdict.cand_nacc);
if (state_based)
a->prop_state_based_acc();
a->new_states(satdict.cand_size);
// Last transition set in the automaton.
unsigned last_aut_trans = -1U;
// Last transition read from the SAT result.
const transition* last_sat_trans = nullptr;
#if DEBUG
std::fstream out("dtgba-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::map<int, acc_cond::mark_t> state_acc;
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)
{
acc_cond::mark_t acc = 0U;
if (state_based)
{
auto i = state_acc.find(t->second.src);
if (i != state_acc.end())
acc = i->second;
}
last_aut_trans = a->new_transition(t->second.src,
t->second.dst,
t->second.cond,
acc);
last_sat_trans = &t->second;
dout << v << '\t' << t->second << "δ\n";
}
}
else
{
dict::rev_acc_map::const_iterator ta;
ta = satdict.revtransaccid.find(v);
// This assumes that the sat solvers output variables in
// increasing order.
if (ta != satdict.revtransaccid.end())
{
dout << v << '\t' << ta->second << "F\n";
if (last_sat_trans &&
ta->second.src == last_sat_trans->src &&
ta->second.cond == last_sat_trans->cond &&
ta->second.dst == last_sat_trans->dst)
{
assert(!state_based);
auto& v = a->trans_data(last_aut_trans).acc;
v |= ta->second.acc;
}
else if (state_based)
{
auto& v = state_acc[ta->second.src];
v |= ta->second.acc;
}
}
}
}
#if DEBUG
dout << "--- pathid variables ---\n";
for (auto pit: satdict.pathid)
if (positive.find(pit.second) != positive.end())
dout << pit.second << '\t' << pit.first << "C\n";
#endif
a->merge_transitions();
return a;
}
}
twa_graph_ptr
dtgba_sat_synthetize(const const_twa_graph_ptr& a,
unsigned target_acc_number,
int target_state_number, bool state_based)
{
if (!a->acc().is_generalized_buchi())
throw std::runtime_error
("dtgba_sat() can only work with generalized Büchi acceptance");
if (target_state_number == 0)
return nullptr;
trace << "dtgba_sat_synthetize(..., acc = " << target_acc_number
<< ", states = " << target_state_number
<< ", state_based = " << state_based << ")\n";
dict d(a);
d.cand_size = target_state_number;
d.cand_nacc = target_acc_number;
satsolver solver;
satsolver::solution_pair solution;
timer_map t;
t.start("encode");
sat_stats s = dtgba_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)
{
tgba_sub_statistics st = sub_stats_reachable(res);
out << st.states << ',' << st.transitions
<< ',' << st.sub_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)
dotty_reachable(std::cout, res);
trace << "dtgba_sat_synthetize(...) = " << res << '\n';
return res;
}
twa_graph_ptr
dtgba_sat_minimize(const const_twa_graph_ptr& a,
unsigned target_acc_number,
bool state_based)
{
int n_states = stats_reachable(a).states;
twa_graph_ptr prev = nullptr;
for (;;)
{
auto next =
dtgba_sat_synthetize(prev ? prev : a, target_acc_number,
--n_states, state_based);
if (!next)
return prev;
else
n_states = stats_reachable(next).states;
prev = next;
}
SPOT_UNREACHABLE();
}
twa_graph_ptr
dtgba_sat_minimize_dichotomy(const const_twa_graph_ptr& a,
unsigned target_acc_number,
bool state_based)
{
int 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 =
dtgba_sat_synthetize(prev ? prev : a, target_acc_number, target,
state_based);
if (!next)
{
min_states = target + 1;
}
else
{
prev = next;
max_states = stats_reachable(next).states - 1;
}
}
return prev;
}
}

73
src/twaalgos/dtgbasat.hh Normal file
View file

@ -0,0 +1,73 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2013, 2014 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 am equivalent deterministic TGBA
/// with a SAT solver.
///
/// \param a the input TGBA. It should be a deterministic TGBA.
///
/// \param target_acc_number is the number of acceptance sets wanted
/// in the result.
///
/// \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, effectively
/// turning the TGBA into a TBA.
///
/// This functions attempts to find a TGBA with \a target_acc_number
/// acceptance sets and target_state_number states that is
/// equivalent to \a a. If no such TGBA is found, a null pointer is
/// returned.
SPOT_API twa_graph_ptr
dtgba_sat_synthetize(const const_twa_graph_ptr& a,
unsigned target_acc_number,
int target_state_number,
bool state_based = false);
/// \brief Attempt to minimize a deterministic TGBA with a SAT solver.
///
/// This calls dtgba_sat_synthetize() in a loop, with a decreasing
/// number of states, and returns the last successfully built TGBA.
///
/// If no smaller TGBA exist, this returns a null pointer.
SPOT_API twa_graph_ptr
dtgba_sat_minimize(const const_twa_graph_ptr& a,
unsigned target_acc_number,
bool state_based = false);
/// \brief Attempt to minimize a deterministic TGBA with a SAT solver.
///
/// This calls dtgba_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
dtgba_sat_minimize_dichotomy(const const_twa_graph_ptr& a,
unsigned target_acc_number,
bool state_based = false);
}

93
src/twaalgos/dupexp.cc Normal file
View file

@ -0,0 +1,93 @@
// -*- 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 "dupexp.hh"
#include "twa/twagraph.hh"
#include <sstream>
#include <string>
#include <map>
#include "reachiter.hh"
#include "dotty.hh"
namespace spot
{
namespace
{
template <class T>
class dupexp_iter: public T
{
public:
dupexp_iter(const const_twa_ptr& a, twa::prop_set p)
: T(a), out_(make_twa_graph(a->get_dict()))
{
out_->copy_acceptance_of(a);
out_->copy_ap_of(a);
out_->prop_copy(a, p);
}
twa_graph_ptr
result()
{
return out_;
}
virtual void
process_state(const state*, int n, twa_succ_iterator*)
{
unsigned ns = out_->new_state();
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_transition
(in - 1, out - 1, si->current_condition(),
si->current_acceptance_conditions());
}
protected:
twa_graph_ptr out_;
};
} // anonymous
twa_graph_ptr
tgba_dupexp_bfs(const const_twa_ptr& aut, twa::prop_set p)
{
dupexp_iter<tgba_reachable_iterator_breadth_first> di(aut, p);
di.run();
return di.result();
}
twa_graph_ptr
tgba_dupexp_dfs(const const_twa_ptr& aut, twa::prop_set p)
{
dupexp_iter<tgba_reachable_iterator_depth_first> di(aut, p);
di.run();
return di.result();
}
}

42
src/twaalgos/dupexp.hh Normal file
View file

@ -0,0 +1,42 @@
// -*- 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,
/// numbering states in bread first order as they are processed.
SPOT_API twa_graph_ptr
tgba_dupexp_bfs(const const_twa_ptr& aut, twa::prop_set p);
/// \ingroup twa_misc
/// \brief Build an explicit automaton from all states of \a aut,
/// numbering states in depth first order as they are processed.
SPOT_API twa_graph_ptr
tgba_dupexp_dfs(const const_twa_ptr& aut, twa::prop_set p);
}

386
src/twaalgos/emptiness.cc Normal file
View file

@ -0,0 +1,386 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2009, 2011, 2012, 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/>.
#include <sstream>
#include "emptiness.hh"
#include "twa/twa.hh"
#include "twa/bddprint.hh"
#include "twaalgos/gtec/gtec.hh"
#include "twaalgos/gv04.hh"
#include "twaalgos/magic.hh"
#include "twaalgos/se05.hh"
#include "twaalgos/tau03.hh"
#include "twaalgos/tau03opt.hh"
namespace spot
{
// tgba_run
//////////////////////////////////////////////////////////////////////
tgba_run::~tgba_run()
{
for (steps::const_iterator i = prefix.begin(); i != prefix.end(); ++i)
i->s->destroy();
for (steps::const_iterator i = cycle.begin(); i != cycle.end(); ++i)
i->s->destroy();
}
tgba_run::tgba_run(const tgba_run& run)
{
for (steps::const_iterator i = run.prefix.begin();
i != run.prefix.end(); ++i)
{
step s = { i->s->clone(), i->label, i->acc };
prefix.push_back(s);
}
for (steps::const_iterator i = run.cycle.begin();
i != run.cycle.end(); ++i)
{
step s = { i->s->clone(), i->label, i->acc };
cycle.push_back(s);
}
}
tgba_run&
tgba_run::operator=(const tgba_run& run)
{
if (&run != this)
{
this->~tgba_run();
new(this) tgba_run(run);
}
return *this;
}
// print_tgba_run
//////////////////////////////////////////////////////////////////////
std::ostream&
print_tgba_run(std::ostream& os,
const const_twa_ptr& a,
const const_tgba_run_ptr& run)
{
bdd_dict_ptr d = a->get_dict();
os << "Prefix:" << std::endl;
for (tgba_run::steps::const_iterator i = run->prefix.begin();
i != run->prefix.end(); ++i)
{
os << " " << a->format_state(i->s) << std::endl;
os << " | ";
bdd_print_formula(os, d, i->label);
os << '\t';
os << a->acc().format(i->acc);
os << std::endl;
}
os << "Cycle:" << std::endl;
for (tgba_run::steps::const_iterator i = run->cycle.begin();
i != run->cycle.end(); ++i)
{
os << " " << a->format_state(i->s) << std::endl;
os << " | ";
bdd_print_formula(os, d, i->label);
os << '\t';
os << a->acc().format(i->acc);
os << '\n';
}
return os;
}
// emptiness_check_result
//////////////////////////////////////////////////////////////////////
tgba_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 0;
}
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 0;
}
}
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 = 0;
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;
}
// tgba_run_to_tgba
//////////////////////////////////////////////////////////////////////
twa_graph_ptr
tgba_run_to_tgba(const const_twa_ptr& a, const const_tgba_run_ptr& run)
{
auto d = a->get_dict();
auto res = make_twa_graph(d);
res->copy_ap_of(a);
res->copy_acceptance_of(a);
const state* s = a->get_init_state();
unsigned src;
unsigned dst;
const tgba_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 (run->prefix.empty())
l = &run->cycle;
else
l = &run->prefix;
tgba_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 == &run->prefix)
{
l = &run->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: a->succ(s))
{
if (j->current_condition() != label
|| j->current_acceptance_conditions() != acc)
continue;
const state* s2 = j->current_state();
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_transition(src, dst, label, acc);
src = dst;
// Sum acceptance conditions.
if (l == &run->cycle && i != l->begin())
seen_acc |= acc;
}
s->destroy();
assert(a->acc().accepting(seen_acc));
return res;
}
}

325
src/twaalgos/emptiness.hh Normal file
View file

@ -0,0 +1,325 @@
// -*- 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 tgba_run;
typedef std::shared_ptr<tgba_run> tgba_run_ptr;
typedef std::shared_ptr<const tgba_run> const_tgba_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::tgba_run. \ref tgba_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 tgba_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 TGBA runs and supporting functions
/// \ingroup emptiness_check
/// @{
/// An accepted run, for a tgba.
struct SPOT_API tgba_run
{
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;
~tgba_run();
tgba_run()
{
};
tgba_run(const tgba_run& run);
tgba_run& operator=(const tgba_run& run);
};
/// \brief Display a tgba_run.
///
/// Output the prefix and cycle parts of the tgba_run \a run on \a os.
///
/// The automaton \a a 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 tgba_run you are trying to print appears to connect states
/// of \a a that are not connected.
///
/// This is unlike replay_tgba_run(), which will ensure the run
/// actually exists in the automaton (and will also display any
/// transition annotation).
SPOT_API std::ostream&
print_tgba_run(std::ostream& os,
const const_twa_ptr& a,
const const_tgba_run_ptr& run);
/// \brief Return an explicit_tgba corresponding to \a run (i.e. comparable
/// states are merged).
///
/// \pre \a run must correspond to an actual run of the automaton \a a.
SPOT_API twa_graph_ptr
tgba_run_to_tgba(const const_twa_ptr& a, const const_tgba_run_ptr& run);
/// @}
/// \addtogroup emptiness_check_stats Emptiness-check statistics
/// \ingroup emptiness_check
}

View file

@ -0,0 +1,273 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2015 Laboratoire de Recherche et Développement de
// l'Epita (LRDE).
// Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6
// (LIP6), département Systèmes Répartis Coopératifs (SRC), Université
// Pierre et Marie Curie.
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include <cassert>
#include <map>
#include "misc/ltstr.hh"
namespace spot
{
/// \addtogroup emptiness_check_stats
/// @{
struct unsigned_statistics
{
virtual
~unsigned_statistics()
{
}
unsigned
get(const char* str) const
{
auto i = stats.find(str);
assert(i != stats.end());
return (this->*i->second)();
}
typedef unsigned (unsigned_statistics::*unsigned_fun)() const;
typedef std::map<const char*, unsigned_fun, char_ptr_less_than> stats_map;
stats_map stats;
};
/// \brief comparable statistics
///
/// This must be built from a spot::unsigned_statistics. But unlike
/// spot::unsigned_statistics, it supports equality and inequality tests.
/// (It's the only operations it supports, BTW.)
class unsigned_statistics_copy
{
public:
unsigned_statistics_copy()
: set(false)
{
}
unsigned_statistics_copy(const unsigned_statistics& o)
: set(false)
{
seteq(o);
}
bool
seteq(const unsigned_statistics& o)
{
if (!set)
{
for (auto& i: o.stats)
stats[i.first] = (o.*i.second)();
set = true;
return true;
}
if (*this == o)
return true;
return false;
}
typedef std::map<const char*, unsigned, char_ptr_less_than> stats_map;
stats_map stats;
bool
operator==(const unsigned_statistics_copy& o) const
{
for (auto& i: stats)
{
auto i2 = o.stats.find(i.first);
if (i2 == o.stats.end())
return false;
if (i.second != i2->second)
return false;
}
return true;
}
bool
operator!=(const unsigned_statistics_copy& o) const
{
return !(*this == o);
}
bool set;
};
/// \brief Emptiness-check statistics
///
/// Implementations of spot::emptiness_check may also implement
/// this interface. Try to dynamic_cast the spot::emptiness_check
/// pointer to know whether these statistics are available.
class ec_statistics: public unsigned_statistics
{
public :
ec_statistics()
: states_(0), transitions_(0), depth_(0), max_depth_(0)
{
stats["states"] =
static_cast<unsigned_statistics::unsigned_fun>(&ec_statistics::states);
stats["transitions"] =
static_cast<unsigned_statistics::unsigned_fun>
(&ec_statistics::transitions);
stats["max. depth"] =
static_cast<unsigned_statistics::unsigned_fun>
(&ec_statistics::max_depth);
}
void
set_states(unsigned n)
{
states_ = n;
}
void
inc_states()
{
++states_;
}
void
inc_transitions()
{
++transitions_;
}
void
inc_depth(unsigned n = 1)
{
depth_ += n;
if (depth_ > max_depth_)
max_depth_ = depth_;
}
void
dec_depth(unsigned n = 1)
{
assert(depth_ >= n);
depth_ -= n;
}
unsigned
states() const
{
return states_;
}
unsigned
transitions() const
{
return transitions_;
}
unsigned
max_depth() const
{
return max_depth_;
}
unsigned
depth() const
{
return depth_;
}
private :
unsigned states_; /// number of disctint visited states
unsigned transitions_; /// number of visited transitions
unsigned depth_; /// maximal depth of the stack(s)
unsigned max_depth_; /// maximal depth of the stack(s)
};
/// \brief Accepting Run Search statistics.
///
/// Implementations of spot::emptiness_check_result may also implement
/// this interface. Try to dynamic_cast the spot::emptiness_check_result
/// pointer to know whether these statistics are available.
class ars_statistics: public unsigned_statistics
{
public:
ars_statistics()
: prefix_states_(0), cycle_states_(0)
{
stats["(non unique) states for prefix"] =
static_cast<unsigned_statistics::unsigned_fun>
(&ars_statistics::ars_prefix_states);
stats["(non unique) states for cycle"] =
static_cast<unsigned_statistics::unsigned_fun>
(&ars_statistics::ars_cycle_states);
}
void
inc_ars_prefix_states()
{
++prefix_states_;
}
unsigned
ars_prefix_states() const
{
return prefix_states_;
}
void
inc_ars_cycle_states()
{
++cycle_states_;
}
unsigned
ars_cycle_states() const
{
return cycle_states_;
}
private:
unsigned prefix_states_; /// states visited to construct the prefix
unsigned cycle_states_; /// states visited to construct the cycle
};
/// \brief Accepting Cycle Search Space statistics
///
/// Implementations of spot::emptiness_check_result may also implement
/// this interface. Try to dynamic_cast the spot::emptiness_check_result
/// pointer to know whether these statistics are available.
class acss_statistics: public ars_statistics
{
public:
acss_statistics()
{
stats["search space states"] =
static_cast<unsigned_statistics::unsigned_fun>
(&acss_statistics::acss_states);
}
virtual
~acss_statistics()
{
}
/// Number of states in the search space for the accepting cycle.
virtual unsigned acss_states() const = 0;
};
/// @}
}

6
src/twaalgos/gtec/.gitignore vendored Normal file
View file

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

View file

@ -0,0 +1,39 @@
## -*- 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

236
src/twaalgos/gtec/ce.cc Normal file
View file

@ -0,0 +1,236 @@
// -*- 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, tgba_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(tgba_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;
}
tgba_run_ptr
couvreur99_check_result::accepting_run()
{
run_ = std::make_shared<tgba_run>();
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 (tgba_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.
tgba_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 0;
// Stay in the final SCC.
if (i->second < scc_root)
return 0;
r->inc_ars_cycle_states();
return i->first;
}
virtual bool
match(tgba_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;
}
}

56
src/twaalgos/gtec/ce.hh Normal file
View file

@ -0,0 +1,56 @@
// -*- 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 tgba_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_;
tgba_run_ptr run_;
};
}

617
src/twaalgos/gtec/gtec.cc Normal file
View file

@ -0,0 +1,617 @@
// -*- 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();
state* s = i->current_state();
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_false())
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.
{
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->current_state();
acc_cond::mark_t acc = succ->current_acceptance_conditions();
// ... 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->current_acceptance_conditions(),
iter->current_state());
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_false())
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);
}
}

244
src/twaalgos/gtec/gtec.hh Normal file
View file

@ -0,0 +1,244 @@
// -*- 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

@ -0,0 +1,90 @@
// -*- 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

@ -0,0 +1,79 @@
// -*- 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

@ -0,0 +1,58 @@
// -*- 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

@ -0,0 +1,58 @@
// -*- 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;
};
}

414
src/twaalgos/gv04.cc Normal file
View file

@ -0,0 +1,414 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2008, 2010, 2011, 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/>.
//#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->acc().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->current_state();
bool acc =
a_->acc().accepting(iter->current_acceptance_conditions());
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 0;
}
void
push(const state* s, bool accepting)
{
trace << " push(s = " << a_->format_state(s)
<< ", accepting = " << accepting << ")\n";
h[s] = ++top;
stack_entry ss = { s, 0, 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 tgba_run_ptr
accepting_run()
{
auto res = std::make_shared<tgba_run>();
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)
{
tgba_run::step st =
{
data.stack[father].s->clone(),
data.stack[father].lasttr->current_condition(),
data.stack[father].lasttr->current_acceptance_conditions()
};
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 0;
}
r->inc_ars_cycle_states();
s->destroy();
return j->first;
}
virtual bool
match(tgba_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(tgba_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_->acc().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);
}
}

57
src/twaalgos/gv04.hh Normal file
View file

@ -0,0 +1,57 @@
// -*- 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());
}

446
src/twaalgos/hoa.cc Normal file
View file

@ -0,0 +1,446 @@
// -*- 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 "ltlast/atomic_prop.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 use_implicit_labels;
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)
{
check_det_and_comp(aut);
use_implicit_labels = implicit && is_deterministic && is_complete;
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;
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;
for (auto& t: aut->out(src))
{
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 (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 declares that it is deterministic or
// state-based, make sure that it really is.
assert(!aut->is_deterministic() || deterministic);
assert(!aut->has_state_based_acc() || state_acc);
}
void number_all_ap()
{
sup_map::iterator i;
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&
hoa_reachable(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;
if (opt)
while (*opt)
{
switch (*opt++)
{
case 'i':
implicit_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;
}
}
// 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);
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)
{
auto f = ltl::is_atomic_prop(d->bdd_map[i].f);
assert(f);
escape_str(os << " \"", f->name()) << '"';
}
os << nl;
unsigned num_acc = aut->acc().num_sets();
if (aut->acc().is_generalized_buchi())
{
if (aut->acc().is_true())
os << "acc-name: all";
else if (aut->acc().is_buchi())
os << "acc-name: Buchi";
else
os << "acc-name: generalized-Buchi " << num_acc;
os << nl;
}
os << "Acceptance: " << num_acc << ' ';
os << aut->acc().get_acceptance();
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;
};
implicit_labels = md.use_implicit_labels;
if (implicit_labels)
prop(" implicit-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_complete)
prop(" complete");
if (md.is_deterministic)
prop(" deterministic");
if (aut->is_stutter_invariant())
prop(" stutter-invariant");
if (aut->is_inherently_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: " << 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)
{
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
{
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&
hoa_reachable(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 hoa_reachable(os, a, opt);
}
}

42
src/twaalgos/hoa.hh Normal file
View file

@ -0,0 +1,42 @@
// -*- 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, (s) state-based acceptance, (t)
/// transition-based acceptance, (m) mixed acceptance, (l)
/// single-line output.
SPOT_API std::ostream&
hoa_reachable(std::ostream& os,
const const_twa_ptr& g,
const char* opt);
}

87
src/twaalgos/isdet.cc Normal file
View file

@ -0,0 +1,87 @@
// -*- 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->is_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;
}
}

53
src/twaalgos/isdet.hh Normal file
View file

@ -0,0 +1,53 @@
// -*- 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);
/// @}
}

120
src/twaalgos/isweakscc.cc Normal file
View file

@ -0,0 +1,120 @@
// -*- 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 "ltlast/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_->trans_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));
}
}

70
src/twaalgos/isweakscc.hh Normal file
View file

@ -0,0 +1,70 @@
// -*- 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);
/// @}
}

146
src/twaalgos/lbtt.cc Normal file
View file

@ -0,0 +1,146 @@
// -*- 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 "ltlvisit/lbt.hh"
#include "ltlparse/public.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);
}
bool
state_is_accepting(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_is_accepting(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);
bool accepting = it->first()
&& aut_->acc().accepting(it->current_acceptance_conditions());
aut_->release_iter(it);
return accepting;
}
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_)
{
// We support only one acceptance condition in the
// state-based format.
if (state_is_accepting(s))
body_ << " 0 -1";
else
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->current_acceptance_conditions()))
body_ << s << ' ';
body_ << "-1 ";
}
const ltl::formula* f = bdd_to_formula(si->current_condition(),
aut_->get_dict());
to_lbt_string(f, body_);
f->destroy();
body_ << '\n';
}
void
end()
{
os_ << seen.size() << ' ';
if (sba_format_)
os_ << '1';
else
os_ << aut_->acc().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&
lbtt_reachable(std::ostream& os, const const_twa_ptr& g, bool sba)
{
if (!g->acc().is_generalized_buchi())
throw std::runtime_error
("LBTT only supports generalized Büchi acceptance");
lbtt_bfs b(g, os, sba);
b.run();
return os;
}
}

39
src/twaalgos/lbtt.hh Normal file
View file

@ -0,0 +1,39 @@
// -*- 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 sba Assume \a g is an SBA and use LBTT's state-based
/// acceptance format (similar to LBT's format).
SPOT_API std::ostream&
lbtt_reachable(std::ostream& os, const const_twa_ptr& g, bool sba = false);
}

429
src/twaalgos/ltl2taa.cc Normal file
View file

@ -0,0 +1,429 @@
// -*- 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 "ltlast/visitor.hh"
#include "ltlast/allnodes.hh"
#include "ltlvisit/lunabbrev.hh"
#include "ltlvisit/tunabbrev.hh"
#include "ltlvisit/nenoform.hh"
#include "ltlvisit/tostring.hh"
#include "ltlvisit/contain.hh"
#include "ltl2taa.hh"
namespace spot
{
namespace
{
using namespace ltl;
/// \brief Recursively translate a formula into a TAA.
class ltl2taa_visitor : public 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_(), to_free_()
{
}
virtual
~ltl2taa_visitor()
{
}
taa_tgba_formula_ptr&
result()
{
for (unsigned i = 0; i < to_free_.size(); ++i)
to_free_[i]->destroy();
res_->set_init_state(init_);
return res_;
}
void
visit(const atomic_prop* node)
{
const formula* f = node; // Handle negation
if (negated_)
{
f = unop::instance(unop::Not, node->clone());
to_free_.push_back(f);
}
init_ = f;
std::vector<const formula*> empty;
taa_tgba::transition* t = res_->create_transition(init_, empty);
res_->add_condition(t, f->clone());
succ_state ss = { empty, f, empty };
succ_.push_back(ss);
}
void
visit(const constant* node)
{
init_ = node;
switch (node->val())
{
case constant::True:
{
std::vector<const formula*> empty;
res_->create_transition(init_, empty);
succ_state ss = { empty, node, empty };
succ_.push_back(ss);
return;
}
case constant::False:
return;
case constant::EmptyWord:
SPOT_UNIMPLEMENTED();
}
SPOT_UNREACHABLE();
}
void
visit(const unop* node)
{
negated_ = node->op() == unop::Not;
ltl2taa_visitor v = recurse(node->child());
init_ = node;
switch (node->op())
{
case unop::X:
{
std::vector<const formula*> dst;
std::vector<const formula*> a;
if (v.succ_.empty()) // Handle X(0)
return;
dst.push_back(v.init_);
res_->create_transition(init_, dst);
succ_state ss =
{ dst, constant::true_instance(), a };
succ_.push_back(ss);
return;
}
case unop::F:
case unop::G:
SPOT_UNIMPLEMENTED(); // TBD
return;
case unop::Not:
// Done in recurse
succ_ = v.succ_;
return;
case unop::Closure:
case unop::NegClosure:
case unop::NegClosureMarked:
SPOT_UNIMPLEMENTED();
}
SPOT_UNREACHABLE();
}
void
visit(const bunop*)
{
SPOT_UNIMPLEMENTED();
}
void
visit(const binop* node)
{
ltl2taa_visitor v1 = recurse(node->first());
ltl2taa_visitor v2 = recurse(node->second());
init_ = node;
std::vector<succ_state>::iterator i1;
std::vector<succ_state>::iterator i2;
taa_tgba::transition* t = 0;
bool contained = false;
bool strong = false;
switch (node->op())
{
case binop::U:
strong = true;
// fall thru
case binop::W:
if (refined_)
contained = lcc_->contained(node->second(), node->first());
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(node->second());
t = res_->create_transition(init_, i1->Q);
res_->add_condition(t, i1->condition->clone());
if (strong)
res_->add_acceptance_condition(t, node->second()->clone());
else
for (unsigned i = 0; i < i1->acc.size(); ++i)
res_->add_acceptance_condition(t, i1->acc[i]->clone());
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->clone());
succ_.push_back(*i2);
}
return;
case binop::M: // Strong Release
strong = true;
case binop::R: // Weak Release
if (refined_)
contained = lcc_->contained(node->first(), node->second());
for (i2 = v2.succ_.begin(); i2 != v2.succ_.end(); ++i2)
{
for (i1 = v1.succ_.begin(); i1 != v1.succ_.end(); ++i1)
{
std::vector<const formula*> u; // Union
std::vector<const formula*> a; // Acceptance conditions
std::copy(i1->Q.begin(), i1->Q.end(), ii(u, u.end()));
const formula* f = i1->condition->clone(); // Refined rule
if (!refined_ || !contained)
{
std::copy(i2->Q.begin(), i2->Q.end(), ii(u, u.end()));
f = multop::instance(multop::And, f,
i2->condition->clone());
}
to_free_.push_back(f);
t = res_->create_transition(init_, u);
res_->add_condition(t, f->clone());
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->clone());
if (strong)
{
i2->acc.push_back(node->first());
res_->add_acceptance_condition(t, node->first()->clone());
}
else if (refined_)
for (unsigned i = 0; i < i2->acc.size(); ++i)
res_->add_acceptance_condition(t, i2->acc[i]->clone());
succ_.push_back(*i2);
}
return;
case binop::Xor:
case binop::Implies:
case binop::Equiv:
case binop::UConcat:
case binop::EConcat:
case binop::EConcatMarked:
SPOT_UNIMPLEMENTED();
}
SPOT_UNREACHABLE();
}
void
visit(const multop* node)
{
bool ok = true;
std::vector<ltl2taa_visitor> vs;
for (unsigned n = 0; n < node->size(); ++n)
{
vs.push_back(recurse(node->nth(n)));
if (vs[n].succ_.empty()) // Handle 0
ok = false;
}
init_ = node;
std::vector<succ_state>::iterator i;
taa_tgba::transition* t = 0;
switch (node->op())
{
case multop::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<const formula*> v; // All sub initial states.
sort(p[n].Q.begin(), p[n].Q.end());
for (unsigned m = 0; m < node->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() == node->size())
{
std::vector<const 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->clone());
for (unsigned i = 0; i < p[n].acc.size(); ++i)
res_->add_acceptance_condition(t, p[n].acc[i]->clone());
succ_.push_back(p[n]);
continue;
}
}
t = res_->create_transition(init_, p[n].Q);
res_->add_condition(t, p[n].condition->clone());
succ_.push_back(p[n]);
}
return;
}
case multop::Or:
for (unsigned n = 0; n < node->size(); ++n)
for (i = vs[n].succ_.begin(); i != vs[n].succ_.end(); ++i)
{
t = res_->create_transition(init_, i->Q);
res_->add_condition(t, i->condition->clone());
succ_.push_back(*i);
}
return;
case multop::Concat:
case multop::Fusion:
case multop::AndNLM:
case multop::AndRat:
case multop::OrRat:
SPOT_UNIMPLEMENTED();
}
SPOT_UNREACHABLE();
}
ltl2taa_visitor
recurse(const formula* f)
{
ltl2taa_visitor v(res_, lcc_, refined_, negated_);
f->accept(v);
for (unsigned i = 0; i < v.to_free_.size(); ++i)
to_free_.push_back(v.to_free_[i]);
return v;
}
private:
taa_tgba_formula_ptr res_;
bool refined_;
bool negated_;
language_containment_checker* lcc_;
typedef std::insert_iterator<std::vector<const formula*>> ii;
struct succ_state
{
std::vector<const formula*> Q; // States
const formula* condition;
std::vector<const formula*> acc;
};
const formula* init_;
std::vector<succ_state> succ_;
std::vector<const formula*> to_free_;
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<const formula*> u; // Union
std::vector<const formula*> a; // Acceptance conditions
const formula* f = constant::true_instance();
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 = multop::instance(multop::And, ss.condition->clone(), f);
for (unsigned i = 0; i < ss.acc.size(); ++i)
{
const formula* g = ss.acc[i]->clone();
a.push_back(g);
to_free_.push_back(g);
}
}
to_free_.push_back(f);
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(const ltl::formula* f,
const bdd_dict_ptr& dict, bool refined_rules)
{
// TODO: s/unabbreviate_ltl/unabbreviate_logic/
const ltl::formula* f1 = ltl::unabbreviate_ltl(f);
const ltl::formula* f2 = ltl::negative_normal_form(f1);
f1->destroy();
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);
f2->accept(v);
auto taa = v.result(); // Careful: before the destroy!
f2->destroy();
delete lcc;
taa->acc().set_generalized_buchi();
return taa;
}
}

53
src/twaalgos/ltl2taa.hh Normal file
View file

@ -0,0 +1,53 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2009, 2010, 2013, 2014 Laboratoire de Recherche et
// Développement de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include "ltlast/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(const ltl::formula* f, const bdd_dict_ptr& dict,
bool refined_rules = false);
}

2526
src/twaalgos/ltl2tgba_fm.cc Normal file

File diff suppressed because it is too large Load diff

131
src/twaalgos/ltl2tgba_fm.hh Normal file
View file

@ -0,0 +1,131 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2010, 2011, 2012, 2013, 2014 Laboratoire de Recherche et
// Développement de l'Epita (LRDE).
// 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 "ltlast/formula.hh"
#include "twa/twagraph.hh"
#include "ltlvisit/apcollect.hh"
#include "ltlvisit/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::ltl::ltl_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 */
///
/// \return A spot::twa_graph that recognizes the language of \a f.
SPOT_API twa_graph_ptr
ltl_to_tgba_fm(const ltl::formula* f, const bdd_dict_ptr& dict,
bool exprop = false, bool symb_merge = true,
bool branching_postponement = false,
bool fair_loop_approx = false,
const ltl::atomic_prop_set* unobs = 0,
ltl::ltl_simplifier* simplifier = 0);
}

608
src/twaalgos/magic.cc Normal file
View file

@ -0,0 +1,608 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2011, 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/>.
//#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->acc().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->current_state();
trace << " Visit the successor: "
<< a_->format_state(s_prime) << std::endl;
bdd label = f.it->current_condition();
auto acc = f.it->current_acceptance_conditions();
// 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->current_state();
trace << " Visit the successor: "
<< a_->format_state(s_prime) << std::endl;
bdd label = f.it->current_condition();
auto acc = f.it->current_acceptance_conditions();
// 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 tgba_run_ptr accepting_run()
{
assert(!ms_->st_blue.empty());
assert(!ms_->st_red.empty());
auto run = std::make_shared<tgba_run>();
typename stack_type::const_reverse_iterator i, j, end;
tgba_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)
{
tgba_run::step s = { i->s->clone(), j->label, j->acc };
l->push_back(s);
}
l = &run->cycle;
j = ms_->st_red.rbegin();
tgba_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)
{
tgba_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 tgba_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 == 0;
}
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(0);
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);
}
}

143
src/twaalgos/magic.hh Normal file
View file

@ -0,0 +1,143 @@
// -*- 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());
/// @}
}

70
src/twaalgos/mask.cc Normal file
View file

@ -0,0 +1,70 @@
// -*- 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->acc().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;
}
}

149
src/twaalgos/mask.hh Normal file
View file

@ -0,0 +1,149 @@
// -*- 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 transition of the automaton \a old, into the 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 transitions. Set the condition to bddfalse to remove it
/// (this will also remove the destination state and its descendants
/// if they are not reachable by another transition).
/// \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_transition(new_src,
new_state(t.dst),
cond, acc);
}
}
}
/// \brief Copy an automaton and update each transitions.
///
/// Copy the states of the automaton \a old, into the 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 transitions. 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->transitions())
{
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_transition(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 transitions that are in 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);
}

680
src/twaalgos/minimize.cc Normal file
View file

@ -0,0 +1,680 @@
// -*- 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 "ltlast/allnodes.hh"
#include "misc/hash.hh"
#include "misc/bddlt.hh"
#include "twaalgos/product.hh"
#include "twaalgos/powerset.hh"
#include "twaalgos/gtec/gtec.hh"
#include "twaalgos/safety.hh"
#include "twaalgos/sccfilter.hh"
#include "twaalgos/sccinfo.hh"
#include "twaalgos/ltl2tgba_fm.hh"
#include "twaalgos/bfssteps.hh"
#include "twaalgos/isdet.hh"
#include "twaalgos/dtgbacomp.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.
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->current_state();
// 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
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_based_acc();
// 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::iterator hit;
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->current_state();
hash_map::const_iterator i = state_num.find(dst);
dst->destroy();
if (i == state_num.end()) // Ignore useless destinations.
continue;
res->new_acc_transition(src_num, i->second,
succit->current_condition(), accepting);
}
}
res->merge_transitions();
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;
}
namespace
{
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 0;
return s;
}
virtual bool
match(tgba_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);
tgba_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());
tgba_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_transition(n - 1, n, i->label);
i->s->destroy();
}
assert(i != loop.end());
loop_a->new_transition(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;
}
}
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->current_state();
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->current_condition());
}
// 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();
res->prop_inherently_weak();
res->prop_state_based_acc();
return res;
}
twa_graph_ptr minimize_wdba(const const_twa_graph_ptr& a)
{
if (a->acc().uses_fin_acceptance())
throw std::runtime_error
("minimize_wdba cannot work with Fin acceptance");
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);
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 (auto& j: succ)
{
is_useless &= useless[j.dst];
unsigned dj = d[j.dst];
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();
res->prop_inherently_weak();
return res;
}
twa_graph_ptr
minimize_obligation(const const_twa_graph_ptr& aut_f,
const ltl::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->is_inherently_weak() && aut_f->is_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_guarantee_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.
const ltl::formula* neg_f =
ltl::unop::instance(ltl::unop::Not, f->clone());
aut_neg_f = ltl_to_tgba_fm(neg_f, aut_f->get_dict());
neg_f->destroy();
// 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 = dtgba_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_guarantee_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->is_inherently_weak());
auto neg_min_aut_f = dtgba_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);
}
}

158
src/twaalgos/minimize.hh Normal file
View file

@ -0,0 +1,158 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Laboratoire de
// Recherche et Développement de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include "twa/twagraph.hh"
#include "ltlast/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,
const ltl::formula* f = 0,
const_twa_graph_ptr aut_neg_f = nullptr,
bool reject_bigger = false);
/// @}
}

View file

@ -0,0 +1,671 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2011, 2013, 2014 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 tgba_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_->acc().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<tgba_run>();
// 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->current_state();
ndfsr_trace << " Visit the successor: "
<< a_->format_state(s_prime) << std::endl;
bdd label = f.it->current_condition();
auto acc = f.it->current_acceptance_conditions();
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, tgba_run::steps& l)
{
const state* s = filter(start);
if (s)
return this->bfs_steps::search(s, l);
else
return 0;
}
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 0;
}
ars->inc_ars_cycle_states();
seen.insert(s);
return s;
}
void finalize(const std::map<const state*, tgba_run::step,
state_ptr_less_than>&,
const tgba_run::step&, const state*, tgba_run::steps&)
{
}
const state_set& get_seen() const
{
return seen;
}
bool match(tgba_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)
{
tgba_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, tgba_run::steps& l)
{
const state* s = filter(start);
if (s)
return this->bfs_steps::search(s, l);
else
return 0;
}
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 0;
}
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(tgba_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(tgba_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) ";
tgba_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;
tgba_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
tgba_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;
tmp.source = tmp.dest = 0; // Initialize to please GCC 4.0.1 (Darwin).
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(tgba_run_ptr run)
{
m_source_trans target;
transition tmp;
tmp.source = tmp.dest = 0; // Initialize to please GCC 4.0.
tmp.acc = 0U;
// Register all states from the cycle as target of the BFS.
for (tgba_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.
tgba_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

217
src/twaalgos/neverclaim.cc Normal file
View file

@ -0,0 +1,217 @@
// -*- 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 "ltlvisit/tostring.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 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
{
std::string label;
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_ << " :: (";
const ltl::formula* f = bdd_to_formula(t.cond, aut_->get_dict());
to_spin_string(f, os_, true);
if (atom)
{
os_ << ") -> assert(!(";
to_spin_string(f, os_, true);
os_ << ")) }";
}
else
{
os_ << ") -> goto ";
print_state(t.dst);
}
f->destroy();
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&
never_claim_reachable(std::ostream& os, const const_twa_ptr& g,
const char* options)
{
if (!(g->acc().is_buchi() || g->acc().is_true()))
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

@ -0,0 +1,45 @@
// -*- 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&
never_claim_reachable(std::ostream& os,
const const_twa_ptr& g,
const char* opt = nullptr);
}

426
src/twaalgos/postproc.cc Normal file
View file

@ -0,0 +1,426 @@
// -*- 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 "dtgbasat.hh"
#include "complete.hh"
#include "totgba.hh"
namespace spot
{
namespace
{
static twa_graph_ptr
ensure_ba(twa_graph_ptr& a)
{
if (a->acc().num_sets() == 0)
{
auto m = a->set_buchi();
for (auto& t: a->transitions())
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)
{
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_ba_simul(const twa_graph_ptr& a, int opt)
{
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_);
if (ba_simul_ <= 0)
return d;
return do_ba_simul(d, ba_simul_);
}
#define PREF_ (pref_ & (Small | Deterministic))
#define COMP_ (pref_ & Complete)
twa_graph_ptr
postprocessor::run(twa_graph_ptr a, const ltl::formula* f)
{
if (type_ != Generic && !a->acc().is_generalized_buchi())
a = to_generalized_buchi(a);
if (PREF_ == Any && level_ == Low)
if (type_ == Generic
|| type_ == TGBA
|| (type_ == BA && a->is_sba())
|| (type_ == Monitor && a->acc().num_sets() == 0))
{
if (COMP_)
a = tgba_complete(a);
return a;
}
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)
state_based_ = true;
int original_acc = a->acc().num_sets();
// Remove useless SCCs.
if (type_ == Monitor)
// Do not bother about acceptance conditions, they will be
// ignored.
a = scc_filter_states(a);
else if (scc_filter_ > 0)
{
if (type_ == BA && a->is_sba())
a = scc_filter_states(a);
else
a = scc_filter(a, scc_filter_ > 1);
}
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 = tgba_complete(a);
return a;
}
if (PREF_ == Any)
{
if (type_ == BA)
a = do_degen(a);
if (COMP_ == Complete)
a = tgba_complete(a);
return a;
}
bool dba_is_wdba = false;
bool dba_is_minimal = false;
twa_graph_ptr dba = 0;
twa_graph_ptr sim = 0;
// (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, 0, reject_bigger);
if (dba && dba->is_inherently_weak() && dba->is_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))
{
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);
}
// 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_ && type_ == BA)
{
dba = do_degen(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->acc().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 to beform tba_determinize_check(). MAke
// sure it is at least 1.
target_acc = original_acc > 0 ? original_acc : 1;
const_twa_graph_ptr in = 0;
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->acc().num_sets() != 1)
in = degeneralize_tba(dba);
else
in = dba;
}
else
{
in = dba;
}
const_twa_graph_ptr res = tgba_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 = dtgba_sat_synthetize(res, target_acc, sat_states_,
state_based_);
else if (sat_minimize_ == 1 || sat_minimize_ == -1)
res = dtgba_sat_minimize(res, target_acc, state_based_);
else // sat_minimize_ == 2
res = dtgba_sat_minimize_dichotomy(res, target_acc, state_based_);
}
if (res)
{
if (state_based_)
// FIXME: This does not simplify generalized acceptance
// conditions, but calling scc_filter() would break the
// BA-typeness of res by removing acceptance marks from
// out-of-SCC transitions.
dba = scc_filter_states(res);
else
dba = 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->acc().num_sets() == 1))
dba = degeneralize(dba);
if (dba && sim)
{
if (dba->num_states() > sim->num_states())
dba = nullptr;
else
sim = nullptr;
}
if (type_ == TGBA && level_ == High && scc_filter_ != 0)
{
if (dba && !dba_is_minimal) // WDBA is already clean.
{
dba = scc_filter(dba, true);
assert(!sim);
}
else if (sim)
{
sim = scc_filter(sim, true);
assert(!dba);
}
}
sim = dba ? dba : sim;
if (COMP_)
sim = tgba_complete(sim);
return sim;
}
}

130
src/twaalgos/postproc.hh Normal file
View file

@ -0,0 +1,130 @@
// -*- 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 = 0);
enum output_type { TGBA, BA, Monitor, Generic };
void
set_type(output_type type)
{
type_ = type;
}
enum
{
Any = 0,
Small = 1,
Deterministic = 2,
// 3 reserved for unambiguous
// Combine Complete as 'Small | Complete' or 'Deterministic | Complete'
Complete = 4
};
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,
const ltl::formula* f);
protected:
twa_graph_ptr do_simul(const twa_graph_ptr& input, int opt);
twa_graph_ptr do_ba_simul(const twa_graph_ptr& input, int opt);
twa_graph_ptr do_degen(const twa_graph_ptr& input);
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_;
};
/// @}
}

441
src/twaalgos/powerset.cc Normal file
View file

@ -0,0 +1,441 @@
// -*- 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/dtgbacomp.hh"
#include "ltlast/unop.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->transitions())
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_transition(src_num, dst_num, num2bdd[c]);
}
}
for (auto v: toclean)
delete v;
if (merge)
res->merge_transitions();
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_trans_data trans;
typedef std::set<trans*> trans_set;
typedef std::vector<trans_set> set_set;
protected:
const_twa_graph_ptr ref_;
power_map& refmap_;
trans_set reject_; // set of rejecting transitions
set_set accept_; // set of cycles that are accepting
trans_set all_; // all non rejecting transitions
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, trans_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->trans_data(i->succ);
loop_a->new_transition(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 trans_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;
trans_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 transitions 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_transitions();
return det;
}
twa_graph_ptr
tba_determinize_check(const twa_graph_ptr& aut,
unsigned threshold_states,
unsigned threshold_cycles,
const ltl::formula* f,
const_twa_graph_ptr neg_aut)
{
if (f == 0 && neg_aut == 0)
return 0;
if (aut->acc().num_sets() > 1)
return 0;
auto det = tba_determinize(aut, threshold_states, threshold_cycles);
if (!det)
return nullptr;
if (neg_aut == nullptr)
{
const ltl::formula* neg_f =
ltl::unop::instance(ltl::unop::Not, f->clone());
neg_aut = ltl_to_tgba_fm(neg_f, aut->get_dict());
neg_f->destroy();
// Remove useless SCCs.
neg_aut = scc_filter(neg_aut, true);
}
if (product(det, neg_aut)->is_empty())
// Complement the DBA.
if (product(aut, dtgba_complement(det))->is_empty())
// Finally, we are now sure that it was safe
// to determinize the automaton.
return det;
return aut;
}
}

139
src/twaalgos/powerset.hh Normal file
View file

@ -0,0 +1,139 @@
// -*- 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,
const ltl::formula* f = 0,
const_twa_graph_ptr neg_aut = 0);
}

113
src/twaalgos/product.cc Normal file
View file

@ -0,0 +1,113 @@
// -*- 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 <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));
}
};
}
twa_graph_ptr product(const const_twa_graph_ptr& left,
const const_twa_graph_ptr& right,
unsigned left_state,
unsigned right_state)
{
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->acc().num_sets();
auto right_acc = right->get_acceptance();
right_acc.shift_left(left_num);
right_acc.append_and(left->get_acceptance());
res->set_acceptance(left_num + right->acc().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_false())
// 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_transition(top.second, dst, cond,
res->acc().join(left->acc(), l.acc,
right->acc(), r.acc));
// If right is deterministic, we can abort immediately!
}
}
return res;
}
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());
}
}

42
src/twaalgos/product.hh Normal file
View file

@ -0,0 +1,42 @@
// -*- coding: utf-8 -*-
// Copyright (C) 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 "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);
}

44
src/twaalgos/projrun.cc Normal file
View file

@ -0,0 +1,44 @@
// -*- 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
{
tgba_run_ptr
project_tgba_run(const const_twa_ptr& a_run,
const const_twa_ptr& a_proj,
const const_tgba_run_ptr& run)
{
auto res = std::make_shared<tgba_run>();
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;
}
}

48
src/twaalgos/projrun.hh Normal file
View file

@ -0,0 +1,48 @@
// -*- 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 tgba_run;
/// \ingroup twa_run
/// \brief Project a tgba_run on a tgba.
///
/// If a tgba_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 tgba_run_ptr
project_tgba_run(const const_twa_ptr& a_run,
const const_twa_ptr& a_proj,
const const_tgba_run_ptr& run);
}

281
src/twaalgos/randomgraph.cc Normal file
View file

@ -0,0 +1,281 @@
// -*- 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 "ltlast/atomic_prop.hh"
#include <sstream>
#include <list>
#include <set>
#include <iterator>
#include <vector>
namespace spot
{
namespace
{
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;
}
}
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;
}
acc_cond::mark_t
random_acc_cond(twa_graph_ptr aut, unsigned n_accs, float a)
{
acc_cond::mark_t m = 0U;
for (unsigned i = 0U; i < n_accs; ++i)
if (drand() < a)
m |= aut->acc().mark(i);
return m;
}
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 ltl::atomic_prop_set* ap, const bdd_dict_ptr& dict,
unsigned n_accs, float a, float t,
bool deterministic, bool state_acc)
{
assert(n > 0);
auto res = make_twa_graph(dict);
if (deterministic)
res->prop_deterministic();
if (state_acc)
res->prop_state_based_acc();
int props_n = ap->size();
int* props = new int[props_n];
int pi = 0;
for (auto i: *ap)
props[pi++] = dict->register_proposition(i, res);
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
// transitions 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 = random_acc_cond(res, n_accs, a);
for (auto& l: labels)
{
if (!state_acc)
m = random_acc_cond(res, 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_transition(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_transition(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;
}
acc_cond::acc_code random_acceptance(unsigned n_accs)
{
// With 0 acceptance sets, we always generate the true acceptance.
// (Working with false is somehow pointless, and the formulas we
// generate for n_accs>0 are always satisfiable, so it makes sense
// that it should be satisfiable for n_accs=0 as well.)
if (n_accs == 0)
return {};
acc_cond acc(n_accs);
std::vector<acc_cond::acc_code> codes;
codes.reserve(n_accs);
for (unsigned i = 0; i < n_accs; ++i)
if (drand() < 0.5)
codes.push_back(acc.inf(acc.mark(i)));
else
codes.push_back(acc.fin(acc.mark(i)));
int s = codes.size();
while (s > 1)
{
// Pick a random code and put it at the end
int p1 = mrand(s--);
std::swap(codes[p1], codes[s]);
// and another one
int p2 = mrand(s);
if (drand() < 0.5)
codes[p2].append_or(std::move(codes.back()));
else
codes[p2].append_and(std::move(codes.back()));
codes.pop_back();
}
return codes[0];
}
}

View file

@ -0,0 +1,88 @@
// -*- 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 "ltlvisit/apcollect.hh"
#include "ltlenv/defaultenv.hh"
#include "twa/bdddict.hh"
#include "twa/acc.hh"
namespace spot
{
/// \ingroup twa_misc
/// \brief Construct a tgba 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
///
/// 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.)
SPOT_API twa_graph_ptr
random_graph(int n, float d,
const ltl::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);
/// Build a random acceptance where each acceptance sets is used once.
SPOT_API acc_cond::acc_code random_acceptance(unsigned n_accs);
}

66
src/twaalgos/randomize.cc Normal file
View file

@ -0,0 +1,66 @@
// -*- 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_transitions)
{
if (!randomize_states && !randomize_transitions)
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_transitions)
{
g.remove_dead_transitions_();
auto& v = g.transition_vector();
mrandom_shuffle(v.begin() + 1, v.end());
}
typedef twa_graph::graph_t::trans_storage_t tr_t;
g.sort_transitions_([](const tr_t& lhs, const tr_t& rhs)
{ return lhs.src < rhs.src; });
g.chain_transitions_();
}
}

34
src/twaalgos/randomize.hh Normal file
View file

@ -0,0 +1,34 @@
// -*- coding: utf-8 -*-
// Copyright (C) 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 "twa/twagraph.hh"
namespace spot
{
/// \brief Randomize a TGBA
///
/// Make a random permutation of the state, and of the transitions
/// leaving this state.
SPOT_API void
randomize(twa_graph_ptr& aut,
bool randomize_states = true,
bool randomize_transitions = true);
}

302
src/twaalgos/reachiter.cc Normal file
View file

@ -0,0 +1,302 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2009, 2011, 2013, 2014 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();
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->current_state();
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 0;
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();
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->current_state();
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();
}
}

201
src/twaalgos/reachiter.hh Normal file
View file

@ -0,0 +1,201 @@
// -*- 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_;
};
}

186
src/twaalgos/reducerun.cc Normal file
View file

@ -0,0 +1,186 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2011, 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/>.
#include "misc/hash.hh"
#include "emptiness.hh"
#include "twa/twa.hh"
#include "bfssteps.hh"
#include "reducerun.hh"
namespace spot
{
namespace
{
class shortest_path: public bfs_steps
{
public:
shortest_path(const const_twa_ptr& a)
: bfs_steps(a), target(0)
{
}
~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, tgba_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(tgba_run::step&, const state* dest)
{
return target->find(dest) != target->end();
}
private:
state_set seen;
const state_set* target;
};
}
tgba_run_ptr
reduce_run(const const_twa_ptr& a, const const_tgba_run_ptr& org)
{
auto res = std::make_shared<tgba_run>();
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;
tgba_run::steps::const_iterator seg = org->cycle.end();
do
{
assert(seg != org->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 != org->cycle.end());
seen_acc |= seg->acc;
tgba_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 == org->cycle.end() ? org->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 (tgba_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.
tgba_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;
}
}

38
src/twaalgos/reducerun.hh Normal file
View file

@ -0,0 +1,38 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2010, 2013, 2014 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 "misc/common.hh"
#include "twa/fwd.hh"
#include "twaalgos/emptiness.hh"
namespace spot
{
/// \ingroup twa_run
/// \brief Reduce an accepting run.
///
/// Return a run which is accepting for \a a and that is no longer
/// than \a org.
SPOT_API tgba_run_ptr
reduce_run(const const_twa_ptr& a, const const_tgba_run_ptr& org);
}

43
src/twaalgos/relabel.cc Normal file
View file

@ -0,0 +1,43 @@
// -*- 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, ltl::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 = d->register_proposition(p.first, aut);
int newv = d->register_proposition(p.second, aut);
bdd_setpair(pairs, oldv, newv);
vars.push_back(oldv);
}
for (auto& t: aut->transitions())
t.cond = bdd_replace(t.cond, pairs);
for (auto v: vars)
d->unregister_variable(v, aut);
}
}

31
src/twaalgos/relabel.hh Normal file
View file

@ -0,0 +1,31 @@
// -*- 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 "ltlvisit/relabel.hh"
#include "twa/twagraph.hh"
namespace spot
{
/// replace atomic propositions in an automaton
SPOT_API void
relabel_here(twa_graph_ptr& aut,
ltl::relabeling_map* relmap);
}

340
src/twaalgos/remfin.cc Normal file
View file

@ -0,0 +1,340 @@
// -*- 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"
//#define TRACE
#ifdef TRACE
#define trace std::cerr
#else
#define trace while (0) std::cerr
#endif
namespace spot
{
namespace
{
// 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(3) | 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;
}
}
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);
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_true())
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';
scc_info si(aut);
unsigned nst = aut->num_states();
auto res = make_twa_graph(aut->get_dict());
res->copy_ap_of(aut);
res->prop_copy(aut, { false, false, false, true });
res->new_states(nst);
res->set_acceptance(aut->acc().num_sets() + extra_sets, new_code);
res->set_init_state(aut->get_init_state_number());
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;
for (unsigned i = 0; i < cs; ++i)
if (!(m & rem[i]))
{
main_sets |= keep[i];
main_add |= add[i];
}
trace << "main_sets " << main_sets << "\nmain_add " << main_add << '\n';
// Create the main copy
for (auto s: states)
for (auto& t: aut->out(s))
res->new_transition(s, t.dst, t.cond,
(t.acc & main_sets) | main_add);
if (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_transition(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.
//
// The acceptance marks on these transition
// are useless, but we keep them to preserve
// state-based acceptance if any.
if (t.dst <= s)
res->new_transition(s, nd, t.cond,
(t.acc & main_sets) | main_add);
}
}
}
}
res->purge_dead_states();
trace << "before cleanup: " << res->get_acceptance() << '\n';
cleanup_acceptance_here(res);
trace << "after cleanup: " << res->get_acceptance() << '\n';
return res;
}
}

29
src/twaalgos/remfin.hh Normal file
View file

@ -0,0 +1,29 @@
// -*- 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);
}

188
src/twaalgos/remprop.cc Normal file
View file

@ -0,0 +1,188 @@
// -*- 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 "ltlenv/defaultenv.hh"
#include "twaalgos/mask.hh"
#include "misc/casts.hh"
#include "ltlvisit/tostring.hh"
#include <ctype.h>
#include <sstream>
namespace spot
{
remove_ap::~remove_ap()
{
for (auto& ap: props_exist)
ap->destroy();
for (auto& ap: props_pos)
ap->destroy();
for (auto& ap: props_neg)
ap->destroy();
}
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& env = spot::ltl::default_environment::instance();
std::vector<const spot::ltl::atomic_prop*> group;
auto start = arg;
while (*start)
{
while (*start == ' ' || *start == '\t')
++start;
if (!*start)
break;
if (*start == ',' || *start == '=')
unexpected_char(arg, start);
const spot::ltl::atomic_prop* 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);
auto* t = env.require(ap);
the_ap = down_cast<const spot::ltl::atomic_prop*>(t);
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);
auto* t = env.require(ap);
the_ap = down_cast<const spot::ltl::atomic_prop*>(t);
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;
}
}

44
src/twaalgos/remprop.hh Normal file
View file

@ -0,0 +1,44 @@
// -*- 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 "ltlast/atomic_prop.hh"
#include "twa/twagraph.hh"
namespace spot
{
class SPOT_API remove_ap
{
std::set<const ltl::atomic_prop*> props_exist;
std::set<const ltl::atomic_prop*> props_pos;
std::set<const ltl::atomic_prop*> props_neg;
public:
~remove_ap();
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;
};
}

259
src/twaalgos/replayrun.cc Normal file
View file

@ -0,0 +1,259 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2011, 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/>.
#include "misc/hash.hh"
#include "replayrun.hh"
#include "twa/twa.hh"
#include "emptiness.hh"
#include "twa/bddprint.hh"
#include <sstream>
namespace spot
{
namespace
{
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
replay_tgba_run(std::ostream& os, const const_twa_ptr& a,
const const_tgba_run_ptr& run, bool debug)
{
const state* s = a->get_init_state();
int serial = 1;
const tgba_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 (run->prefix.empty())
{
l = &run->cycle;
in = "cycle";
if (!debug)
os << "No prefix.\nCycle:\n";
}
else
{
l = &run->prefix;
in = "prefix";
if (!debug)
os << "Prefix:\n";
}
tgba_run::steps::const_iterator i = l->begin();
if (s->compare(i->s))
{
if (debug)
os << "ERROR: First state of run (in " << in << "): "
<< a->format_state(i->s)
<< "\ndoes not match initial state of automata: "
<< a->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 << a->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 == &run->prefix)
{
l = &run->cycle;
in = "cycle";
i = l->begin();
if (!debug)
os << "Cycle:\n";
}
next = l->begin()->s;
}
// browse the actual outgoing transitions
twa_succ_iterator* j = a->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->current_condition() != label
|| j->current_acceptance_conditions() != acc)
continue;
const state* s2 = j->current_state();
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(a->get_dict(), label)
<< " and acc=" << a->acc().format(acc)
<< " leaving state " << serial
<< " for state " << a->format_state(next) << '\n'
<< "The following transitions leave state " << serial
<< ":\n";
if (j->first())
do
{
const state* s2 = j->current_state();
os << " *";
print_annotation(os, a, j);
os << " label="
<< bdd_format_formula(a->get_dict(),
j->current_condition())
<< " and acc="
<< a->acc().format(j->current_acceptance_conditions())
<< " going to " << a->format_state(s2) << '\n';
s2->destroy();
}
while (j->next());
}
a->release_iter(j);
s->destroy();
return false;
}
if (debug)
{
os << "transition";
print_annotation(os, a, j);
os << " with label="
<< bdd_format_formula(a->get_dict(), label)
<< " and acc=" << a->acc().format(acc)
<< std::endl;
}
else
{
os << " | ";
print_annotation(os, a, j);
bdd_print_formula(os, a->get_dict(), label);
os << '\t';
a->acc().format(acc);
os << std::endl;
}
a->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 == &run->cycle && i != l->begin())
{
all_acc |= acc;
if (!all_acc_seen && a->acc().accepting(all_acc))
{
all_acc_seen = true;
if (debug)
os << "all acceptance conditions ("
<< a->acc().format(all_acc)
<< ") have been seen\n";
}
}
}
s->destroy();
if (!a->acc().accepting(all_acc))
{
if (debug)
os << "ERROR: The cycle's acceptance conditions ("
<< a->acc().format(all_acc)
<< ") do not\nmatch those of the automaton ("
<< a->acc().format(a->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;
}
}

54
src/twaalgos/replayrun.hh Normal file
View file

@ -0,0 +1,54 @@
// -*- 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 tgba_run;
/// \ingroup twa_run
/// \brief Replay a tgba_run on a tgba.
///
/// This is similar to print_tgba_run(), 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::tgba::transition_annotation()). The output will stop
/// if the run cannot be completed.
///
/// \param run the run to replay
/// \param a the automata on which to replay that run
/// \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
SPOT_API bool
replay_tgba_run(std::ostream& os,
const const_twa_ptr& a,
const const_tgba_run_ptr& run,
bool debug = false);
}

87
src/twaalgos/safety.cc Normal file
View file

@ -0,0 +1,87 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2010, 2011, 2013, 2014, 2015 Laboratoire de Recherche
// et Développement de l'Epita (LRDE)
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "safety.hh"
#include "misc/hash.hh"
#include <deque>
namespace spot
{
bool
is_guarantee_automaton(const const_twa_graph_ptr& aut,
const scc_info* si)
{
if (aut->acc().uses_fin_acceptance())
throw std::runtime_error
("is_guarantee_automaton() does not support Fin acceptance");
// Create an scc_info if the user did not give one to us.
bool need_si = !si;
if (need_si)
si = new scc_info(aut);
bool result = true;
for (auto& scc: *si)
{
if (scc.is_rejecting())
continue;
// Non-rejecting SCCs should necessarily be accepting, because
// with only one self loop, there should be no ambiguity.
if (!scc.is_accepting())
return false;
// Accepting SCCs should have only one state.
auto& st = scc.states();
if (st.size() != 1)
{
result = false;
break;
}
// The state should have only one transition that is a
// self-loop labelled by true.
auto src = st.front();
auto out = aut->out(src);
auto it = out.begin();
assert(it != out.end());
result =
(it->cond == bddtrue) && (it->dst == src) && (++it == out.end());
if (!result)
break;
}
if (need_si)
delete si;
return result;
}
bool is_safety_mwdba(const const_twa_graph_ptr& aut)
{
if (!(aut->acc().is_buchi() || aut->acc().is_true()))
throw std::runtime_error
("is_safety_mwdba() should be called on a Buchi automaton");
for (auto& t: aut->transitions())
if (!aut->acc().accepting(t.acc))
return false;
return true;
}
}

61
src/twaalgos/safety.hh Normal file
View file

@ -0,0 +1,61 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2010, 2011, 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
{
/// \brief Whether an automaton represents a guarantee property.
///
/// A weak deterministic TGBA represents a guarantee property if any
/// accepting path ends on an accepting state with only one
/// transition that is a self-loop labelled by true.
///
/// Note that in the general case, this is only a sufficient
/// condition : some guarantee automata might not be recognized with
/// this check e.g. because of some non-determinism in the
/// automaton. In that case, you should interpret a \c false return
/// value as "I don't know".
///
/// If you apply this function on a weak deterministic TGBA
/// (e.g. after a successful minimization with
/// minimize_obligation()), then the result leaves no doubt: false
/// really means that the automaton is not a guarantee property.
///
/// \param aut the automaton to check
///
/// \param sm an scc_info object for the automaton if available (it
/// will be built otherwise).
SPOT_API bool
is_guarantee_automaton(const const_twa_graph_ptr& aut,
const scc_info* sm = 0);
/// \brief Whether a minimized WDBA represents a safety property.
///
/// A minimized WDBA (as returned by a successful run of
/// minimize_obligation()) represent safety property if it contains
/// only accepting transitions.
///
/// \param aut the automaton to check
SPOT_API bool
is_safety_mwdba(const const_twa_graph_ptr& aut);
}

81
src/twaalgos/sbacc.cc Normal file
View file

@ -0,0 +1,81 @@
// -*- 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->has_state_based_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_based_acc();
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 transition 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->transitions())
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_transition(one.second,
new_state(t.dst, t.acc),
t.cond,
one.first.second);
}
return res;
}
}

30
src/twaalgos/sbacc.hh Normal file
View file

@ -0,0 +1,30 @@
// -*- 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);
}

453
src/twaalgos/scc.cc Normal file
View file

@ -0,0 +1,453 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2008, 2009, 2011, 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 <queue>
#include <set>
#include <iostream>
#include <sstream>
#include "scc.hh"
#include "twa/bddprint.hh"
#include "misc/escape.hh"
namespace spot
{
scc_map::scc_map(const const_twa_ptr& aut)
: aut_(aut)
{
}
scc_map::~scc_map()
{
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();
}
}
unsigned
scc_map::initial() const
{
state* in = aut_->get_init_state();
int val = scc_of_state(in);
in->destroy();
return val;
}
const scc_map::succ_type&
scc_map::succ(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].succ;
}
bool
scc_map::trivial(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].trivial;
}
bool
scc_map::accepting(unsigned n) const
{
if (scc_map_[n].trivial)
return false;
return aut_->acc().accepting(acc_set_of(n));
}
const_twa_ptr
scc_map::get_aut() const
{
return aut_;
}
int
scc_map::relabel_component()
{
assert(!root_.front().states.empty());
std::list<const state*>::iterator i;
int n = scc_map_.size();
for (i = root_.front().states.begin(); i != root_.front().states.end(); ++i)
{
hash_type::iterator spi = h_.find(*i);
assert(spi != h_.end());
assert(spi->first == *i);
assert(spi->second < 0);
spi->second = n;
}
scc_map_.push_back(root_.front());
return n;
}
// recursively update supp rec
bdd
scc_map::update_supp_rec(unsigned state)
{
assert(scc_map_.size() > state);
bdd& res = scc_map_[state].supp_rec;
if (res == bddfalse)
{
const succ_type& s = succ(state);
succ_type::const_iterator it;
res = scc_map_[state].supp;
for (it = s.begin(); it != s.end(); ++it)
res &= update_supp_rec(it->first) & bdd_support(it->second);
}
return res;
}
void
scc_map::build_map()
{
// Setup depth-first search from the initial state.
{
self_loops_ = 0;
state* init = aut_->get_init_state();
num_ = -1;
h_.emplace(init, num_);
root_.emplace_front(num_);
arc_acc_.push(0U);
arc_cond_.push(bddfalse);
twa_succ_iterator* iter = aut_->succ_iter(init);
iter->first();
todo_.emplace(init, iter);
}
while (!todo_.empty())
{
assert(root_.size() == arc_acc_.size());
assert(root_.size() == arc_cond_.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();
// Fill rem with any component removed, so that
// remove_component() does not have to traverse the SCC
// again.
hash_type::const_iterator spi = h_.find(curr);
assert(spi != h_.end());
root_.front().states.push_front(spi->first);
// 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_.front().index == spi->second)
{
assert(!arc_acc_.empty());
assert(arc_cond_.size() == arc_acc_.size());
bdd cond = arc_cond_.top();
arc_cond_.pop();
arc_acc_.pop();
int num = relabel_component();
root_.pop_front();
// Record the transition between the SCC being popped
// and the previous SCC.
if (!root_.empty())
root_.front().succ.emplace(num, cond);
}
aut_->release_iter(succ);
// Do not destroy CURR: it is a key in H.
continue;
}
// We have a successor to look at.
// Fetch the values we are interested in...
const state* dest = succ->current_state();
if (!dest->compare(todo_.top().first))
++self_loops_;
auto acc = succ->current_acceptance_conditions();
bdd cond = succ->current_condition();
root_.front().supp &= bdd_support(cond);
// ... 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?
hash_type::const_iterator spi = h_.find(dest);
if (spi == h_.end())
{
// Yes. Number it, stack it, and register its successors
// for later processing.
h_.emplace(dest, --num_);
root_.emplace_front(num_);
arc_acc_.push(acc);
arc_cond_.push(cond);
twa_succ_iterator* iter = aut_->succ_iter(dest);
iter->first();
todo_.emplace(dest, iter);
continue;
}
// We already know the state.
dest->destroy();
// Have we reached a maximal SCC?
if (spi->second >= 0)
{
int dest = spi->second;
// Record that there is a transition from this SCC to the
// dest SCC labelled with cond.
succ_type::iterator i = root_.front().succ.find(dest);
if (i == root_.front().succ.end())
root_.front().succ.emplace(dest, cond);
else
i->second |= cond;
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->second;
std::list<const state*> states;
succ_type succs;
cond_set conds;
conds.insert(cond);
bdd supp = bddtrue;
std::set<acc_cond::mark_t> used_acc = { acc };
while (threshold > root_.front().index)
{
assert(!root_.empty());
assert(!arc_acc_.empty());
assert(arc_acc_.size() == arc_cond_.size());
acc |= root_.front().acc;
auto lacc = arc_acc_.top();
acc |= lacc;
used_acc.insert(lacc);
used_acc.insert(root_.front().useful_acc.begin(),
root_.front().useful_acc.end());
states.splice(states.end(), root_.front().states);
succs.insert(root_.front().succ.begin(),
root_.front().succ.end());
conds.insert(arc_cond_.top());
conds.insert(root_.front().conds.begin(),
root_.front().conds.end());
supp &= root_.front().supp;
root_.pop_front();
arc_acc_.pop();
arc_cond_.pop();
}
// Note that we do not always have
// threshold == root_.front().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_.front().acc |= acc;
root_.front().states.splice(root_.front().states.end(), states);
root_.front().succ.insert(succs.begin(), succs.end());
root_.front().conds.insert(conds.begin(), conds.end());
root_.front().supp &= supp;
// This SCC is no longer trivial.
root_.front().trivial = false;
assert(!used_acc.empty());
root_.front().useful_acc.insert(used_acc.begin(), used_acc.end());
}
// recursively update supp_rec
(void) update_supp_rec(initial());
}
unsigned scc_map::scc_of_state(const state* s) const
{
hash_type::const_iterator i = h_.find(s);
assert(i != h_.end());
return i->second;
}
const scc_map::cond_set& scc_map::cond_set_of(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].conds;
}
bdd scc_map::ap_set_of(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].supp;
}
bdd scc_map::aprec_set_of(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].supp_rec;
}
acc_cond::mark_t scc_map::acc_set_of(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].acc;
}
unsigned scc_map::self_loops() const
{
return self_loops_;
}
const std::list<const state*>& scc_map::states_of(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].states;
}
const state* scc_map::one_state_of(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].states.front();
}
unsigned scc_map::scc_count() const
{
return scc_map_.size();
}
const std::set<acc_cond::mark_t>&
scc_map::useful_acc_of(unsigned n) const
{
assert(scc_map_.size() > n);
return scc_map_[n].useful_acc;
}
std::ostream&
dump_scc_dot(const scc_map& m, std::ostream& out, bool verbose)
{
out << "digraph G {\n i [label=\"\", style=invis, height=0]" << std::endl;
int start = m.initial();
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();
const scc_map::cond_set& cs = m.cond_set_of(state);
std::ostringstream ostr;
ostr << state;
if (verbose)
{
size_t n = m.states_of(state).size();
ostr << " (" << n << " state";
if (n > 1)
ostr << 's';
ostr << ")\\naccs=";
escape_str(ostr, m.get_aut()->acc().format(m.acc_set_of(state)));
ostr << "\\nconds=[";
for (scc_map::cond_set::const_iterator i = cs.begin();
i != cs.end(); ++i)
{
if (i != cs.begin())
ostr << ", ";
escape_str(ostr,
bdd_format_formula(m.get_aut()->get_dict(), *i));
}
ostr << "]\\n AP=[";
escape_str(ostr,
bdd_format_sat(m.get_aut()->get_dict(),
m.ap_set_of(state)));
ostr << "]\\n APrec=[";
escape_str(ostr, bdd_format_sat(m.get_aut()->get_dict(),
m.aprec_set_of(state)));
ostr << "]\\n useful=[";
for (auto a: m.useful_acc_of(state))
m.get_aut()->acc().format(a);
ostr << ']';
}
out << " " << state << " [shape=box,"
<< (m.accepting(state) ? "style=bold," : "")
<< "label=\"" << ostr.str() << "\"]" << std::endl;
const scc_map::succ_type& succ = m.succ(state);
scc_map::succ_type::const_iterator it;
for (it = succ.begin(); it != succ.end(); ++it)
{
int dest = it->first;
bdd label = it->second;
out << " " << state << " -> " << dest
<< " [label=\"";
escape_str(out, bdd_format_formula(m.get_aut()->get_dict(), label));
out << "\"]" << std::endl;
if (seen[dest])
continue;
seen[dest] = true;
q.push(dest);
}
}
out << '}' << std::endl;
return out;
}
std::ostream&
dump_scc_dot(const const_twa_ptr& a, std::ostream& out, bool verbose)
{
scc_map m(a);
m.build_map();
return dump_scc_dot(m, out, verbose);
}
}

210
src/twaalgos/scc.hh Normal file
View file

@ -0,0 +1,210 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2008, 2009, 2010, 2011, 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 <map>
#include <stack>
#include <vector>
#include "twa/twa.hh"
#include <iosfwd>
#include "misc/hash.hh"
#include "misc/bddlt.hh"
namespace spot
{
/// Build a map of Strongly Connected components in in a TGBA.
class SPOT_API scc_map
{
public:
typedef std::map<unsigned, bdd> succ_type;
typedef std::set<bdd, bdd_less_than> cond_set;
/// \brief Constructor.
///
/// This will note compute the map initially. You should call
/// build_map() to do so.
scc_map(const const_twa_ptr& aut);
~scc_map();
/// Actually compute the graph of strongly connected components.
void build_map();
/// Get the automaton for which the map has been constructed.
const_twa_ptr get_aut() const;
/// \brief Get the number of SCC in the automaton.
///
/// SCCs are labelled from 0 to scc_count()-1.
///
/// \pre This should only be called once build_map() has run.
unsigned scc_count() const;
/// \brief Get number of the SCC containing the initial state.
///
/// \pre This should only be called once build_map() has run.
unsigned initial() const;
/// \brief Successor SCCs of a SCC.
///
/// \pre This should only be called once build_map() has run.
const succ_type& succ(unsigned n) const;
/// \brief Return whether an SCC is trivial.
///
/// Trivial SCCs have one state and no self-loop.
///
/// \pre This should only be called once build_map() has run.
bool trivial(unsigned n) const;
/// \brief Return whether an SCC is accepting.
///
/// \pre This should only be called once build_map() has run.
bool accepting(unsigned n) const;
/// \brief Return the set of conditions occurring in an SCC.
///
/// \pre This should only be called once build_map() has run.
const cond_set& cond_set_of(unsigned n) const;
/// \brief Return the set of atomic properties occurring on the
/// transitions leaving states from SCC \a n.
///
/// The transitions considered are all transitions inside SCC
/// \a n, as well as the transitions leaving SCC \a n.
///
/// \return a BDD that is a conjuction of all atomic properties
/// occurring on the transitions leaving the states of SCC \a n.
///
/// \pre This should only be called once build_map() has run.
bdd ap_set_of(unsigned n) const;
/// \brief Return the set of atomic properties reachable from this SCC.
///
/// \return a BDD that is a conjuction of all atomic properties
/// occurring on the transitions reachable from this SCC n.
///
/// \pre This should only be called once build_map() has run.
bdd aprec_set_of(unsigned n) const;
/// \brief Return the set of acceptance conditions occurring in an SCC.
///
/// \pre This should only be called once build_map() has run.
acc_cond::mark_t acc_set_of(unsigned n) const;
/// \brief Return the set of useful acceptance conditions of SCC \a n.
///
/// Useless acceptances conditions are always implied by other acceptances
/// conditions. This returns all the other acceptance conditions.
const std::set<acc_cond::mark_t>& useful_acc_of(unsigned n) const;
/// \brief Return the set of states of an SCC.
///
/// The states in the returned list are still owned by the scc_map
/// instance. They should NOT be destroyed by the client code.
///
/// \pre This should only be called once build_map() has run.
const std::list<const state*>& states_of(unsigned n) const;
/// \brief Return one state of an SCC.
///
/// The state in the returned list is still owned by the scc_map
/// instance. It should NOT be destroyed by the client code.
///
/// \pre This should only be called once build_map() has run.
const state* one_state_of(unsigned n) const;
/// \brief Return the number of the SCC a state belongs too.
///
/// \pre This should only be called once build_map() has run.
unsigned scc_of_state(const state* s) const;
/// \brief Return the number of self loops in the automaton.
unsigned self_loops() const;
protected:
bdd update_supp_rec(unsigned state);
int relabel_component();
struct scc
{
public:
scc(int index) : index(index), acc(0U),
supp(bddtrue), supp_rec(bddfalse),
trivial(true) {};
/// Index of the SCC.
int index;
/// The union of all acceptance conditions of transitions which
/// connect the states of the connected component.
acc_cond::mark_t acc;
/// States of the component.
std::list<const state*> states;
/// Set of conditions used in the SCC.
cond_set conds;
/// Conjunction of atomic propositions used in the SCC.
bdd supp;
/// Conjunction of atomic propositions used in the SCC.
bdd supp_rec;
/// Successor SCC.
succ_type succ;
/// Trivial SCC have one state and no self-loops.
bool trivial;
/// \brief Set of acceptance combinations used in the SCC.
std::set<acc_cond::mark_t> useful_acc;
};
const_twa_ptr aut_; // Automata to decompose.
typedef std::list<scc> stack_type;
stack_type root_; // Stack of SCC roots.
std::stack<acc_cond::mark_t> arc_acc_; // A stack of acceptance conditions
// between each of these SCC.
std::stack<bdd> arc_cond_; // A stack of conditions
// between each of these SCC.
typedef std::unordered_map<const state*, int,
state_ptr_hash, state_ptr_equal> hash_type;
hash_type h_; // Map of visited states. Values >= 0
// designate maximal SCC. Values < 0
// number states that are part of
// incomplete SCCs being completed.
int num_; // Number of visited nodes, negated.
typedef std::pair<const spot::state*, twa_succ_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.
typedef std::vector<scc> scc_map_type;
scc_map_type scc_map_; // Map of constructed maximal SCC.
// SCC number "n" in H_ corresponds to entry
// "n" in SCC_MAP_.
unsigned self_loops_; // Self loops count.
};
SPOT_API std::ostream&
dump_scc_dot(const const_twa_ptr& a,
std::ostream& out, bool verbose = false);
SPOT_API std::ostream&
dump_scc_dot(const scc_map& m, std::ostream& out, bool verbose = false);
}

379
src/twaalgos/sccfilter.cc Normal file
View file

@ -0,0 +1,379 @@
// -*- 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
// transition 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;
}
unsigned accsets(unsigned n)
{
return n;
}
// Accept all transitions, 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 not in an accepting 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 transitions outside of
// non-accepting SCCs.
template <class next_filter = id_filter>
struct acc_filter_all: next_filter
{
template<typename... Args>
acc_filter_all(scc_info* si, Args&&... args)
: next_filter(si, std::forward<Args>(args)...)
{
}
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);
// If the transition is between two SCCs, or in a
// non-accepting SCC. Remove the acceptance sets.
if (this->si->is_rejecting_scc(u) || u != this->si->scc_of(dst))
acc = 0U;
}
return filtered_trans(keep, cond, acc);
}
};
// Remove acceptance conditions from all transitions whose
// destination is not an accepting SCCs.
template <class next_filter = id_filter>
struct acc_filter_some: next_filter
{
template<typename... Args>
acc_filter_some(scc_info* si, Args&&... args)
: next_filter(si, std::forward<Args>(args)...)
{
}
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 (this->si->is_rejecting_scc(this->si->scc_of(dst)))
acc = 0U;
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)...)
{
}
unsigned accsets(unsigned n)
{
unsigned scc_count = this->si->scc_count();
auto& acc = this->si->get_aut()->acc();
assert(n == acc.num_sets());
(void) n;
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]);
}
return 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)
{
if (!aut->acc().is_generalized_buchi())
throw std::runtime_error
("scc_filter() works only with generalized Büchi acceptance");
twa_graph_ptr filtered = make_twa_graph(aut->get_dict());
unsigned in_n = aut->num_states(); // Number of input states.
if (in_n == 0) // Nothing to filter.
return filtered;
filtered->copy_ap_of(aut);
// Compute scc_info if not supplied.
scc_info* si = given_si;
if (!si)
si = new scc_info(aut);
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);
filtered->
set_generalized_buchi(filter.accsets(aut->acc().num_sets()));
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_transition(osrc, odst, cond, acc);
}
}
if (!given_si)
delete si;
// If the initial state has been filtered out, we don't attempt
// to fix it.
auto init = inout[aut->get_init_state_number()];
if (init < out_n)
filtered->set_init_state(init);
return filtered;
}
}
twa_graph_ptr
scc_filter_states(const const_twa_graph_ptr& aut, scc_info* given_si)
{
auto res = scc_filter_apply<state_filter<>>(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;
if (remove_all_useless)
res = scc_filter_apply<state_filter
<acc_filter_all
<acc_filter_simplify<>>>>(aut, given_si);
else
res = scc_filter_apply<state_filter
<acc_filter_some
<acc_filter_simplify<>>>>(aut, given_si);
res->merge_transitions();
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_all
<acc_filter_simplify<>>>>>(aut, given_si,
suspvars,
ignoredvars,
early_susp);
else
res = scc_filter_apply<susp_filter
<state_filter
<acc_filter_some
<acc_filter_simplify<>>>>>(aut, given_si,
suspvars,
ignoredvars,
early_susp);
res->merge_transitions();
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;
}
}

89
src/twaalgos/sccfilter.hh Normal file
View file

@ -0,0 +1,89 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2009, 2010, 2012, 2013, 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 "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, 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 non-accepting SCC
/// 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 TGBA 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 = 0);
/// \brief Prune unaccepting SCCs.
///
/// This is an abridged version of scc_filter(), that only remove
/// useless states, without touching at the acceptance conditions.
///
/// Especially, if the input TGBA has the SBA property, (i.e.,
/// transitions leaving accepting states are all marked as
/// accepting), then the output TGBA will also have that property.
SPOT_API twa_graph_ptr
scc_filter_states(const const_twa_graph_ptr& aut, scc_info* given_si = 0);
/// \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 = 0);
}

356
src/twaalgos/sccinfo.cc Normal file
View file

@ -0,0 +1,356 @@
// -*- 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 "misc/escape.hh"
namespace spot
{
namespace
{
struct scc
{
public:
scc(int index, bdd in_cond, acc_cond::mark_t in_acc):
index(index), in_cond(in_cond), in_acc(in_acc)
{
}
int index; // Index of the SCC
bdd in_cond; // Condition on incoming transition
acc_cond::mark_t in_acc; // Acceptance sets on the incoming transition
scc_info::scc_node node;
};
}
scc_info::scc_info(const_twa_graph_ptr aut)
: aut_(aut)
{
unsigned n = aut->num_states();
sccof_.resize(n, -1U);
typedef std::list<scc> stack_type;
stack_type root_; // Stack of SCC roots.
std::stack<std::pair<bdd, bdd>> arc_; // A stack of acceptance conditions
// between each of these SCC.
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_front(num_, bddfalse, 0U);
todo_.emplace(init, aut->out(init).begin());
}
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();
// Fill STATES with any state removed, so that
// remove_component() does not have to traverse the SCC
// again.
root_.front().node.states_.push_front(curr);
// 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_.front().index == h_[curr])
{
int num = node_.size();
for (auto s: root_.front().node.states_)
{
sccof_[s] = num;
h_[s] = num + 1;
}
bdd cond = root_.front().in_cond;
auto acc = root_.front().node.acc_marks();
bool triv = root_.front().node.is_trivial();
node_.emplace_back(acc, triv);
std::swap(node_.back().succ_, root_.front().node.succ_);
std::swap(node_.back().states_, root_.front().node.states_);
node_.back().accepting_ =
!triv && root_.front().node.accepting_;
node_.back().rejecting_ =
triv || !aut->acc().inf_satisfiable(acc);
root_.pop_front();
// Record the transition between the SCC being popped
// and the previous SCC.
if (!root_.empty())
root_.front().node.succ_.emplace_back(cond, num);
}
continue;
}
// We have a successor to look at.
// Fetch the values we are interested in...
unsigned dest = succ->dst;
auto acc = succ->acc;
bdd cond = succ->cond;
++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_front(num_, cond, acc);
todo_.emplace(dest, aut->out(dest).begin());
continue;
}
// We already know the state.
// Have we reached a maximal SCC?
if (spi > 0)
{
--spi;
// Record that there is a transition from this SCC to the
// dest SCC labelled with cond.
auto& succ = root_.front().node.succ_;
scc_succs::iterator i = std::find_if(succ.begin(), succ.end(),
[spi](const scc_trans& x) {
return (x.dst ==
(unsigned) spi);
});
if (i == succ.end())
succ.emplace_back(cond, spi);
else
i->cond |= cond;
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;
std::list<unsigned> states;
scc_succs succs;
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_.front().index)
{
acc |= root_.front().node.acc_;
acc |= root_.front().in_acc;
is_accepting |= root_.front().node.accepting_;
states.splice(states.end(), root_.front().node.states_);
succs.insert(succs.end(),
root_.front().node.succ_.begin(),
root_.front().node.succ_.end());
root_.pop_front();
assert(!root_.empty());
}
// Note that we do not always have
// threshold == root_.front().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_.front().node.acc_ |= acc;
root_.front().node.accepting_ |= is_accepting
|| aut->acc().accepting(root_.front().node.acc_);
root_.front().node.states_.splice(root_.front().node.states_.end(),
states);
root_.front().node.succ_.insert(root_.front().node.succ_.end(),
succs.begin(), succs.end());
// This SCC is no longer trivial.
root_.front().node.trivial_ = false;
}
// 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;
}
for (auto j: node_[i].succ())
if (node_[j.dst].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;
}
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;
}
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 (auto& i: m->succ(state))
{
int dest = i.dst;
bdd label = i.cond;
out << " " << state << " -> " << dest
<< " [label=\"";
escape_str(out, bdd_format_formula(aut->get_dict(), label));
out << "\"]\n";
if (seen[dest])
continue;
seen[dest] = true;
q.push(dest);
}
}
out << "}\n";
if (!sccinfo)
delete m;
return out;
}
}

231
src/twaalgos/sccinfo.hh Normal file
View file

@ -0,0 +1,231 @@
// -*- 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:
struct scc_trans
{
scc_trans(bdd cond, unsigned dst)
: cond(cond), dst(dst)
{
}
bdd cond;
unsigned dst;
};
typedef std::vector<scc_trans> scc_succs;
class scc_node
{
friend class scc_info;
protected:
scc_succs succ_;
acc_cond::mark_t acc_;
std::list<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), 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::list<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_;
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::list<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();
}
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;
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);
}

700
src/twaalgos/se05.cc Normal file
View file

@ -0,0 +1,700 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2011, 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/>.
//#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->acc().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 0;
}
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->current_state();
trace << " Visit the successor: "
<< a_->format_state(s_prime) << std::endl;
bdd label = f.it->current_condition();
auto acc = f.it->current_acceptance_conditions();
// 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->current_state();
trace << " Visit the successor: "
<< a_->format_state(s_prime) << std::endl;
bdd label = f.it->current_condition();
auto acc = f.it->current_acceptance_conditions();
// 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 tgba_run_ptr accepting_run()
{
assert(!ms_->st_blue.empty());
assert(!ms_->st_red.empty());
auto run = std::make_shared<tgba_run>();
typename stack_type::const_reverse_iterator i, j, end;
tgba_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;
tgba_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();
tgba_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)
{
tgba_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 tgba_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(0)
{
}
color_ref(color* c)
: is_cyan(false), ph(0), phc(0), ps(0), 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 == 0;
}
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(0); // 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(0), ps(0), 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);
}
}

148
src/twaalgos/se05.hh Normal file
View file

@ -0,0 +1,148 @@
// -*- 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);
/// @}
}

Some files were not shown because too many files have changed in this diff Show more