Add a new, parameterized, version of the Couvreur emptiness check.
This version has optimization for explicit twa, and also for weak and terminal (depending on whether an accepting run is requested) automata. * spot/twaalgos/couvreurnew.hh, spot/twaalgos/couvreurnew.cc, spot/twaalgos/Makefile.am: New files for the new algorithm. * spot/twaalgos/emptiness.cc, tests/core/randtgba.cc: Register new algorithm.
This commit is contained in:
parent
f0416b3f3c
commit
1a08eca840
6 changed files with 927 additions and 0 deletions
7
NEWS
7
NEWS
|
|
@ -4,6 +4,13 @@ New in spot 2.2.1.dev (Not yet released)
|
|||
|
||||
* A twa is required to have at least one state, the initial state.
|
||||
|
||||
* The Couvreur emptiness check has been rewritten to use the explicit
|
||||
interface when possible, to avoid overkill memory allocations.
|
||||
The new version has further optimizations for weak and terminal
|
||||
automata. Overall, this new version is roughly 4x faster on explicit
|
||||
automata than the former one. The old version has been kept for
|
||||
compatibility.
|
||||
|
||||
Build:
|
||||
|
||||
* If the system has an installed libltdl library, use it instead of
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ twaalgos_HEADERS = \
|
|||
magic.hh \
|
||||
mask.hh \
|
||||
minimize.hh \
|
||||
couvreurnew.hh \
|
||||
neverclaim.hh \
|
||||
postproc.hh \
|
||||
powerset.hh \
|
||||
|
|
@ -109,6 +110,7 @@ libtwaalgos_la_SOURCES = \
|
|||
magic.cc \
|
||||
mask.cc \
|
||||
minimize.cc \
|
||||
couvreurnew.cc \
|
||||
ndfs_result.hxx \
|
||||
neverclaim.cc \
|
||||
postproc.cc \
|
||||
|
|
|
|||
864
spot/twaalgos/couvreurnew.cc
Normal file
864
spot/twaalgos/couvreurnew.cc
Normal file
|
|
@ -0,0 +1,864 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2016 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 <spot/twaalgos/couvreurnew.hh>
|
||||
|
||||
#include <spot/twa/twa.hh>
|
||||
#include <spot/twa/twagraph.hh>
|
||||
#include <spot/twaalgos/bfssteps.hh>
|
||||
#include <spot/twaalgos/emptiness.hh>
|
||||
#include <spot/twaalgos/emptiness_stats.hh>
|
||||
|
||||
#include <stack>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using explicit_iterator = twa_graph::graph_t::const_iterator;
|
||||
|
||||
// A proxy class that allows to manipulate an iterator from the
|
||||
// explicit interface as an iterator from the abstract interface.
|
||||
class explicitproxy
|
||||
{
|
||||
public:
|
||||
explicitproxy(explicit_iterator it)
|
||||
: it_(it)
|
||||
{}
|
||||
|
||||
const explicitproxy*
|
||||
operator->() const
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
explicitproxy*
|
||||
operator->()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
bool
|
||||
done() const
|
||||
{
|
||||
return !it_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
dst() const
|
||||
{
|
||||
return it_->dst;
|
||||
}
|
||||
|
||||
acc_cond::mark_t
|
||||
acc() const
|
||||
{
|
||||
return it_->acc;
|
||||
}
|
||||
|
||||
void
|
||||
next()
|
||||
{
|
||||
++it_;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit_iterator it_;
|
||||
};
|
||||
|
||||
template<bool is_explicit>
|
||||
class twa_iteration
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
class twa_iteration<false>
|
||||
{
|
||||
public:
|
||||
using state_t = const state*;
|
||||
using iterator_t = twa_succ_iterator*;
|
||||
template<class val>
|
||||
using state_map = state_map<val>;
|
||||
|
||||
template<class val>
|
||||
static
|
||||
std::pair<std::pair<state_t, val>, bool>
|
||||
h_emplace(state_map<val>& h, state_t s, val i)
|
||||
{
|
||||
auto p = h.emplace(s, i);
|
||||
return std::make_pair(*p.first, p.second);
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<state_t, int>
|
||||
h_find(const state_map<int>& h, state_t s)
|
||||
{
|
||||
auto p = h.find(s);
|
||||
if (p == h.end())
|
||||
return std::make_pair(nullptr, 0);
|
||||
else
|
||||
return std::make_pair(p->first, p->second);
|
||||
}
|
||||
|
||||
static
|
||||
unsigned
|
||||
h_count(const state_map<int>& h, const std::function<bool(int)>& p)
|
||||
{
|
||||
unsigned count = 0;
|
||||
for (auto i : h)
|
||||
if (p(i.second))
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
static
|
||||
state_t
|
||||
initial_state(const const_twa_ptr& twa_p)
|
||||
{
|
||||
return twa_p->get_init_state();
|
||||
}
|
||||
|
||||
static
|
||||
iterator_t
|
||||
succ(const const_twa_ptr& twa_p, state_t s)
|
||||
{
|
||||
auto res = twa_p->succ_iter(s);
|
||||
res->first();
|
||||
return res;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
destroy(state_t s)
|
||||
{
|
||||
s->destroy();
|
||||
}
|
||||
|
||||
static
|
||||
const state*
|
||||
to_state(const const_twa_ptr&, state_t s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
static
|
||||
state_t
|
||||
from_state(const const_twa_ptr&, const state* s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
it_destroy(const const_twa_ptr& twa_p, iterator_t it)
|
||||
{
|
||||
twa_p->release_iter(it);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class twa_iteration<true>
|
||||
{
|
||||
public:
|
||||
using state_t = unsigned;
|
||||
using iterator_t = explicitproxy;
|
||||
template<class val>
|
||||
using state_map = std::vector<val>;
|
||||
|
||||
template<class val>
|
||||
static
|
||||
std::pair<std::pair<state_t, val>, bool>
|
||||
h_emplace(state_map<val>& h, state_t s, val i)
|
||||
{
|
||||
if (h[s] == val())
|
||||
{
|
||||
h[s] = i;
|
||||
return std::make_pair(std::make_pair(s, h[s]), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::make_pair(std::make_pair(s, h[s]), false);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<state_t, int>
|
||||
h_find(const state_map<int>& h, state_t s)
|
||||
{
|
||||
assert(s < h.size());
|
||||
return std::make_pair(s, h[s]);
|
||||
}
|
||||
|
||||
static
|
||||
unsigned
|
||||
h_count(const state_map<int>& h, const std::function<bool(int)>& p)
|
||||
{
|
||||
unsigned count = 0;
|
||||
for (auto i : h)
|
||||
if (p(i))
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
static
|
||||
state_t
|
||||
initial_state(const const_twa_graph_ptr& twa_p)
|
||||
{
|
||||
return twa_p->get_init_state_number();
|
||||
}
|
||||
|
||||
static
|
||||
iterator_t
|
||||
succ(const const_twa_graph_ptr& twa_p, state_t s)
|
||||
{
|
||||
return explicitproxy(twa_p->out(s).begin());
|
||||
}
|
||||
|
||||
static
|
||||
const state*
|
||||
to_state(const const_twa_graph_ptr& twa_p, state_t s)
|
||||
{
|
||||
return twa_p->state_from_number(s);
|
||||
}
|
||||
|
||||
static
|
||||
state_t
|
||||
from_state(const const_twa_graph_ptr& twa_p, const state* s)
|
||||
{
|
||||
return twa_p->state_number(s);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
destroy(state_t)
|
||||
{
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
it_destroy(const const_twa_ptr&, iterator_t)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// A simple struct representing an SCC.
|
||||
struct scc
|
||||
{
|
||||
scc(int i): index(i), condition(0U) {}
|
||||
|
||||
int index;
|
||||
acc_cond::mark_t condition;
|
||||
};
|
||||
|
||||
template<bool is_explicit>
|
||||
using automaton_ptr = typename std::conditional<is_explicit,
|
||||
const_twa_graph_ptr,
|
||||
const_twa_ptr>::type;
|
||||
|
||||
// The status of the emptiness-check on success.
|
||||
// It contains everyting needed to build a counter-example:
|
||||
// the automaton, the stack of SCCs traversed by the counter-example,
|
||||
// and the heap of visited states with their indices.
|
||||
template<bool is_explicit>
|
||||
struct couvreur99_new_status
|
||||
{
|
||||
using T = twa_iteration<is_explicit>;
|
||||
|
||||
couvreur99_new_status(const automaton_ptr<is_explicit>& a)
|
||||
: aut(a)
|
||||
{
|
||||
// Appropriate version is resolved through SFINAE.
|
||||
init<is_explicit>();
|
||||
}
|
||||
|
||||
automaton_ptr<is_explicit> aut;
|
||||
std::stack<scc> root;
|
||||
typename T::template state_map<int> h;
|
||||
typename T::state_t cycle_seed;
|
||||
|
||||
private:
|
||||
template<bool U>
|
||||
typename std::enable_if<U>::type
|
||||
init()
|
||||
{
|
||||
// Initialize h with the right size.
|
||||
h.resize(aut->num_states(), 0);
|
||||
}
|
||||
|
||||
template<bool U>
|
||||
typename std::enable_if<!U>::type
|
||||
init()
|
||||
{}
|
||||
};
|
||||
|
||||
template<bool is_explicit>
|
||||
using couvreur99_new_status_ptr =
|
||||
std::shared_ptr<couvreur99_new_status<is_explicit>>;
|
||||
|
||||
template<bool is_explicit>
|
||||
class couvreur99_new_result final :
|
||||
public emptiness_check_result,
|
||||
public acss_statistics
|
||||
{
|
||||
using T = twa_iteration<is_explicit>;
|
||||
public:
|
||||
couvreur99_new_result(const couvreur99_new_status_ptr<is_explicit>& ecs)
|
||||
: emptiness_check_result(ecs->aut), ecs_(ecs)
|
||||
{}
|
||||
|
||||
virtual
|
||||
unsigned
|
||||
acss_states() const override
|
||||
{
|
||||
int scc_root = ecs_->root.top().index;
|
||||
return T::h_count(ecs_->h,
|
||||
[scc_root](int s) { return s >= scc_root; });
|
||||
}
|
||||
|
||||
twa_run_ptr
|
||||
accepting_run() override;
|
||||
private:
|
||||
couvreur99_new_status_ptr<is_explicit> ecs_;
|
||||
twa_run_ptr run_;
|
||||
|
||||
void
|
||||
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 condition.
|
||||
//
|
||||
// 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 = T::to_state(ecs_->aut, ecs_->cycle_seed);
|
||||
const state* cycle_seed = T::to_state(ecs_->aut, ecs_->cycle_seed);
|
||||
do
|
||||
{
|
||||
struct scc_bfs final: bfs_steps
|
||||
{
|
||||
const couvreur99_new_status<is_explicit>* ecs;
|
||||
couvreur99_new_result<is_explicit>* r;
|
||||
acc_cond::mark_t& acc_to_traverse;
|
||||
int scc_root;
|
||||
|
||||
scc_bfs(const couvreur99_new_status<is_explicit>* ecs,
|
||||
couvreur99_new_result<is_explicit>* 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) override
|
||||
{
|
||||
auto i = T::h_find(ecs->h, T::from_state(ecs->aut, s));
|
||||
s->destroy();
|
||||
// Ignore unknown states.
|
||||
if (i.second == 0)
|
||||
return nullptr;
|
||||
// Stay in the final SCC.
|
||||
if (i.second < scc_root)
|
||||
return nullptr;
|
||||
r->inc_ars_cycle_states();
|
||||
return T::to_state(ecs->aut, i.first);
|
||||
}
|
||||
|
||||
virtual bool
|
||||
match(twa_run::step& st, const state* s) override
|
||||
{
|
||||
acc_cond::mark_t less_acc =
|
||||
acc_to_traverse - st.acc;
|
||||
if (less_acc != acc_to_traverse
|
||||
|| (acc_to_traverse == 0U
|
||||
&& T::from_state(ecs->aut, 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 != cycle_seed);
|
||||
}
|
||||
};
|
||||
|
||||
template<bool is_explicit>
|
||||
class shortest_path final : public bfs_steps
|
||||
{
|
||||
using T = twa_iteration<is_explicit>;
|
||||
public:
|
||||
shortest_path(const state_set* t,
|
||||
const couvreur99_new_status_ptr<is_explicit>& ecs,
|
||||
couvreur99_new_result<is_explicit>* r)
|
||||
: bfs_steps(ecs->aut), target(t), ecs(ecs), r(r)
|
||||
{
|
||||
}
|
||||
|
||||
const state*
|
||||
search(const state* start, twa_run::steps& l)
|
||||
{
|
||||
return this->bfs_steps::search(filter(start), l);
|
||||
}
|
||||
|
||||
const state*
|
||||
filter(const state* s) override
|
||||
{
|
||||
r->inc_ars_prefix_states();
|
||||
auto i = T::h_find(ecs->h, T::from_state(ecs->aut, s));
|
||||
s->destroy();
|
||||
// Ignore unknown states ...
|
||||
if (i.second == 0)
|
||||
return nullptr;
|
||||
// ... as well as dead states
|
||||
if (i.second == -1)
|
||||
return nullptr;
|
||||
return T::to_state(ecs->aut, i.first);
|
||||
}
|
||||
|
||||
bool
|
||||
match(twa_run::step&, const state* dest) override
|
||||
{
|
||||
return target->find(dest) != target->end();
|
||||
}
|
||||
private:
|
||||
state_set seen;
|
||||
const state_set* target;
|
||||
couvreur99_new_status_ptr<is_explicit> ecs;
|
||||
couvreur99_new_result<is_explicit>* r;
|
||||
};
|
||||
|
||||
template<bool is_explicit>
|
||||
twa_run_ptr
|
||||
couvreur99_new_result<is_explicit>::accepting_run()
|
||||
{
|
||||
run_ = std::make_shared<twa_run>(ecs_->aut);
|
||||
|
||||
assert(!ecs_->root.empty());
|
||||
|
||||
// Compute an accepting cycle.
|
||||
accepting_cycle();
|
||||
|
||||
// Compute the prefix: it is the shorted path from the initial
|
||||
// state of the automaton to any state of the cycle.
|
||||
|
||||
// Register all states from the cycle as targets of the BFS.
|
||||
state_set ss;
|
||||
for (twa_run::steps::const_iterator i = run_->cycle.begin();
|
||||
i != run_->cycle.end(); ++i)
|
||||
ss.insert(i->s);
|
||||
shortest_path<is_explicit> shpath(&ss, ecs_, this);
|
||||
|
||||
const state* prefix_start =
|
||||
T::to_state(ecs_->aut, T::initial_state(ecs_->aut));
|
||||
// There are two cases: either the initial state is already in
|
||||
// the cycle, or it is not. If it is, we will have to rotate
|
||||
// the cycle so it begins at this position. Otherwise we will shift
|
||||
// the cycle so it begins at 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 in the cycle.
|
||||
prefix_start->destroy();
|
||||
cycle_entry_point = *ps;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This initial state is outside the cycle. Compute the prefix.
|
||||
cycle_entry_point = shpath.search(prefix_start, run_->prefix);
|
||||
}
|
||||
|
||||
// Locate cycle_entry_point on the cycle.
|
||||
twa_run::steps::iterator cycle_ep_it;
|
||||
for (cycle_ep_it = run_->cycle.begin();
|
||||
cycle_ep_it != run_->cycle.end()
|
||||
&& cycle_entry_point->compare(cycle_ep_it->s); ++cycle_ep_it)
|
||||
continue;
|
||||
assert(cycle_ep_it != run_->cycle.end());
|
||||
|
||||
// Now shift the cycle so it starts on cycle_entry_point
|
||||
run_->cycle.splice(run_->cycle.end(), run_->cycle,
|
||||
run_->cycle.begin(), cycle_ep_it);
|
||||
|
||||
return run_;
|
||||
}
|
||||
|
||||
// A simple enum for the different automata strengths.
|
||||
enum twa_strength { STRONG, WEAK, TERMINAL };
|
||||
|
||||
template<bool is_explicit, twa_strength strength>
|
||||
class couvreur99_new : public emptiness_check, public ec_statistics
|
||||
{
|
||||
using T = twa_iteration<is_explicit>;
|
||||
using state_t = typename T::state_t;
|
||||
using iterator_t = typename T::iterator_t;
|
||||
using pair_state_iter = std::pair<state_t, iterator_t>;
|
||||
|
||||
couvreur99_new_status_ptr<is_explicit> ecs_;
|
||||
|
||||
public:
|
||||
couvreur99_new(const automaton_ptr<is_explicit>& a,
|
||||
option_map o = option_map())
|
||||
: emptiness_check(a, o)
|
||||
, ecs_(std::make_shared<couvreur99_new_status<is_explicit>>(a))
|
||||
{
|
||||
}
|
||||
|
||||
virtual
|
||||
emptiness_check_result_ptr
|
||||
check() override
|
||||
{
|
||||
return check_impl<true>();
|
||||
}
|
||||
|
||||
// The following two methods anticipe the future interface of the
|
||||
// class emptiness_check. Following the interface of twa, the class
|
||||
// emptiness_check_result should not be exposed.
|
||||
bool
|
||||
is_empty()
|
||||
{
|
||||
return check_impl<false>();
|
||||
}
|
||||
|
||||
twa_run
|
||||
accepting_run()
|
||||
{
|
||||
return check_impl<true>()->accepting_run();
|
||||
}
|
||||
|
||||
private:
|
||||
// A union-like struct to store the result of an emptiness.
|
||||
// If the caller only wants to test emptiness, it is sufficient to
|
||||
// store the Boolean result.
|
||||
// Otherwise, we need to store all the information needed to build
|
||||
// an accepting run.
|
||||
struct check_result
|
||||
{
|
||||
enum { BOOL, PTR } tag;
|
||||
union
|
||||
{
|
||||
bool res;
|
||||
emptiness_check_result_ptr ecr;
|
||||
};
|
||||
|
||||
// Copy constructor.
|
||||
check_result(const check_result& o)
|
||||
: tag(o.tag)
|
||||
{
|
||||
if (tag == BOOL)
|
||||
res = o.res;
|
||||
else
|
||||
ecr = o.ecr;
|
||||
}
|
||||
check_result(bool r)
|
||||
: tag(BOOL), res(r)
|
||||
{
|
||||
}
|
||||
check_result(std::nullptr_t)
|
||||
: tag(PTR), ecr(nullptr)
|
||||
{
|
||||
}
|
||||
check_result(const emptiness_check_result_ptr& p)
|
||||
: tag(PTR), ecr(p)
|
||||
{
|
||||
}
|
||||
// A destructor to properly dereference the shared_pointer.
|
||||
~check_result()
|
||||
{
|
||||
if (tag == PTR)
|
||||
ecr.~emptiness_check_result_ptr();
|
||||
}
|
||||
|
||||
// Handy cast operators.
|
||||
// Note that a pointer can be cast to a Boolean as usual.
|
||||
operator bool() const
|
||||
{
|
||||
if (tag == BOOL)
|
||||
return res;
|
||||
else
|
||||
return ecr;
|
||||
}
|
||||
operator emptiness_check_result_ptr() const
|
||||
{
|
||||
if (tag == PTR)
|
||||
return ecr;
|
||||
else
|
||||
throw std::runtime_error("accepting run was not requested");
|
||||
}
|
||||
};
|
||||
|
||||
template<bool need_accepting_run>
|
||||
check_result
|
||||
check_impl()
|
||||
{
|
||||
{
|
||||
// check trivial acceptance condition
|
||||
auto acc = ecs_->aut->acc();
|
||||
if (acc.is_f())
|
||||
return nullptr;
|
||||
// check whether fin acceptance conditions are used
|
||||
if (acc.uses_fin_acceptance())
|
||||
throw std::runtime_error
|
||||
("Fin acceptance is not supported by couvreur99()");
|
||||
}
|
||||
|
||||
// We use five main data in this algorithm:
|
||||
// * root, a stack of strongly connected components (SCC),
|
||||
// * h, a hash of all visited nodes with their order,
|
||||
// ("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;
|
||||
// * live, a stack of live nodes
|
||||
std::deque<state_t> live;
|
||||
|
||||
// Setup depth-first search from the initial state.
|
||||
{
|
||||
state_t init = T::initial_state(ecs_->aut);
|
||||
ecs_->h[init] = 1;
|
||||
ecs_->root.push(1);
|
||||
if (strength == STRONG)
|
||||
arc.push(0U);
|
||||
auto iter = T::succ(ecs_->aut, init);
|
||||
todo.emplace(init, iter);
|
||||
live.emplace_back(init);
|
||||
inc_depth();
|
||||
}
|
||||
|
||||
while (!todo.empty())
|
||||
{
|
||||
if (strength == STRONG)
|
||||
assert(ecs_->root.size() == arc.size());
|
||||
|
||||
// We are looking at the next successor in SUCC.
|
||||
auto succ = todo.top().second;
|
||||
|
||||
// If there is no more successors, backtrack.
|
||||
if (succ->done())
|
||||
{
|
||||
// We have explored all successors of state CURR.
|
||||
state_t curr = todo.top().first;
|
||||
|
||||
// Backtrack todo.
|
||||
todo.pop();
|
||||
|
||||
// When backtracking the root of an SCC, we must also remove
|
||||
// that SCC from the ARC/ROOT stacks. We must discard from H
|
||||
// all reachable states from this SCC.
|
||||
assert(!ecs_->root.empty());
|
||||
if (ecs_->root.top().index == ecs_->h[curr])
|
||||
{
|
||||
if (strength == STRONG)
|
||||
{
|
||||
assert(!arc.empty());
|
||||
arc.pop();
|
||||
}
|
||||
// Remove all elements of this SCC from the live stack.
|
||||
auto i = std::find(live.rbegin(), live.rend(), curr);
|
||||
assert(i != live.rend());
|
||||
++i; // Because base() does -1
|
||||
for (auto it = i.base(); it != live.end(); ++it)
|
||||
{
|
||||
ecs_->h[*it] = -1;
|
||||
}
|
||||
live.erase(i.base(), live.end());
|
||||
ecs_->root.pop();
|
||||
}
|
||||
T::it_destroy(ecs_->aut, succ);
|
||||
// Do not destroy curr: it is a key in h.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a successor to look at.
|
||||
inc_transitions();
|
||||
// Fetch the values we are interested in...
|
||||
state_t dest = succ->dst();
|
||||
auto acc = succ->acc();
|
||||
if (!need_accepting_run)
|
||||
if (strength == TERMINAL && ecs_->aut->acc().accepting(acc))
|
||||
{
|
||||
// We have found an accepting SCC.
|
||||
// Release all iterators in todo.
|
||||
while (!todo.empty())
|
||||
{
|
||||
T::it_destroy(ecs_->aut, todo.top().second);
|
||||
todo.pop();
|
||||
dec_depth();
|
||||
}
|
||||
// We do not need an accepting run.
|
||||
return true;
|
||||
}
|
||||
// ... and point the iterator to the next successor, for
|
||||
// the next iteration.
|
||||
todo.top().second->next();
|
||||
|
||||
// We do not need succ from now on.
|
||||
|
||||
// Are we going to a new state?
|
||||
auto p = T::h_emplace(ecs_->h, dest, num+1);
|
||||
if (p.second)
|
||||
{
|
||||
// Yes. Bump number, stack the stack, and register its
|
||||
// successors for later processing.
|
||||
ecs_->root.push(++num);
|
||||
if (strength == STRONG)
|
||||
arc.push(acc);
|
||||
iterator_t iter = T::succ(ecs_->aut, dest);
|
||||
todo.emplace(dest, iter);
|
||||
live.emplace_back(dest);
|
||||
inc_depth();
|
||||
continue;
|
||||
}
|
||||
T::destroy(dest);
|
||||
|
||||
// If we have reached a dead component, ignore it.
|
||||
if (p.first.second == -1)
|
||||
continue;
|
||||
|
||||
// Now this is the most interesting case. We have reach 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 than the one of
|
||||
// the SCC of S2 (called the "threshold").
|
||||
int threshold = p.first.second;
|
||||
// TODO optimize if this is a self-loop?
|
||||
while (threshold < ecs_->root.top().index)
|
||||
{
|
||||
assert(!ecs_->root.empty());
|
||||
if (strength == STRONG)
|
||||
{
|
||||
assert(!arc.empty());
|
||||
acc |= ecs_->root.top().condition;
|
||||
acc |= arc.top();
|
||||
arc.pop();
|
||||
}
|
||||
ecs_->root.pop();
|
||||
}
|
||||
// Note that we do not always have
|
||||
// threshold == 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;
|
||||
|
||||
if (ecs_->aut->acc().accepting(ecs_->root.top().condition))
|
||||
{
|
||||
// We have found an accepting SCC.
|
||||
// Release all iterators in todo.
|
||||
while (!todo.empty())
|
||||
{
|
||||
T::it_destroy(ecs_->aut, 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(states());
|
||||
if (need_accepting_run)
|
||||
return check_result(
|
||||
std::make_shared<couvreur99_new_result<is_explicit>>(ecs_));
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// This automaton recognizes no word.
|
||||
set_states(states());
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
emptiness_check_ptr
|
||||
get_couvreur99_new(const const_twa_ptr& a, spot::option_map o)
|
||||
{
|
||||
const_twa_graph_ptr ag = std::dynamic_pointer_cast<const twa_graph>(a);
|
||||
if (ag)
|
||||
// the automaton is explicit
|
||||
{
|
||||
// NB: The order of the if's matter.
|
||||
if (a->prop_terminal())
|
||||
return std::make_shared<couvreur99_new<true, TERMINAL>>(ag, o);
|
||||
if (a->prop_weak())
|
||||
return std::make_shared<couvreur99_new<true, WEAK>>(ag, o);
|
||||
return std::make_shared<couvreur99_new<true, STRONG>>(ag, o);
|
||||
}
|
||||
else
|
||||
// the automaton is abstract
|
||||
{
|
||||
// NB: The order of the if's matter.
|
||||
if (a->prop_terminal())
|
||||
return std::make_shared<couvreur99_new<false, TERMINAL>>(a, o);
|
||||
if (a->prop_weak())
|
||||
return std::make_shared<couvreur99_new<false, WEAK>>(a, o);
|
||||
return std::make_shared<couvreur99_new<false, STRONG>>(a, o);
|
||||
}
|
||||
}
|
||||
|
||||
emptiness_check_result_ptr
|
||||
couvreur99_new_check(const const_twa_ptr& a)
|
||||
{
|
||||
return get_couvreur99_new(a, spot::option_map())->check();
|
||||
}
|
||||
|
||||
emptiness_check_ptr
|
||||
get_couvreur99_new_abstract(const const_twa_ptr& a, option_map o)
|
||||
{
|
||||
if (a->prop_terminal())
|
||||
return std::make_shared<couvreur99_new<false, TERMINAL>>(a, o);
|
||||
if (a->prop_weak())
|
||||
return std::make_shared<couvreur99_new<false, WEAK>>(a, o);
|
||||
return std::make_shared<couvreur99_new<false, STRONG>>(a, o);
|
||||
}
|
||||
|
||||
} // namespace spot
|
||||
49
spot/twaalgos/couvreurnew.hh
Normal file
49
spot/twaalgos/couvreurnew.hh
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2016 Laboratoire de Recherche et Developpement 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 <spot/twaalgos/emptiness.hh>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
/// \brief A rewritten version of the Couvreur emptiness check.
|
||||
///
|
||||
/// It is optimized to run on explicit automata (avoiding the memory
|
||||
/// allocations of the virtual, abstract interface.
|
||||
/// It also has specializations for weak and terminal automata.
|
||||
SPOT_API
|
||||
emptiness_check_ptr
|
||||
get_couvreur99_new(const const_twa_ptr& a, option_map o);
|
||||
|
||||
/// \brief Same as above, but always uses the abstract interface.
|
||||
///
|
||||
/// This function is provided to test the efficiency of specializing our
|
||||
/// algorithms for explicit automata. It uses the same optimizations for
|
||||
/// weak and terminal automata as the one above.
|
||||
SPOT_API
|
||||
emptiness_check_ptr
|
||||
get_couvreur99_new_abstract(const const_twa_ptr& a, option_map o);
|
||||
|
||||
/// \brief A shortcut to run the optimized emptiness check directly.
|
||||
///
|
||||
/// This is the same as get_couvreur99_new(a, {})->check().
|
||||
SPOT_API
|
||||
emptiness_check_result_ptr
|
||||
couvreur99_new_check(const const_twa_ptr& a);
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
#include <memory>
|
||||
#include <spot/twaalgos/emptiness.hh>
|
||||
#include <spot/twaalgos/bfssteps.hh>
|
||||
#include <spot/twaalgos/couvreurnew.hh>
|
||||
#include <spot/twaalgos/gtec/gtec.hh>
|
||||
#include <spot/twaalgos/gv04.hh>
|
||||
#include <spot/twaalgos/magic.hh>
|
||||
|
|
@ -129,6 +130,8 @@ namespace spot
|
|||
ec_algo ec_algos[] =
|
||||
{
|
||||
{ "Cou99", couvreur99, 0, -1U },
|
||||
{ "Cou99new", get_couvreur99_new, 0, -1U },
|
||||
{ "Cou99abs", get_couvreur99_new_abstract, 0, -1U },
|
||||
{ "CVWY90", magic_search, 0, 1 },
|
||||
{ "GV04", explicit_gv04_check, 0, 1 },
|
||||
{ "SE05", se05, 0, 1 },
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ const char* default_algos[] = {
|
|||
"Cou99(poprem)",
|
||||
"Cou99(poprem shy !group)",
|
||||
"Cou99(poprem shy group)",
|
||||
"Cou99new",
|
||||
"Cou99abs",
|
||||
"CVWY90",
|
||||
"CVWY90(bsh=4K)",
|
||||
"GV04",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue