Introduce an emptiness-check interface, and modify the existing

algorithms to conform to it, uniformly.  This will unfortunately
break third-party code that were using these algorithms.
* src/tgbaalgos/emptiness.cc, src/tgbaalgos/emptiness.hh: New files.
* src/tgbaalgos/Makefile.am: New files.
* src/tgbaalgos/magic.cc, src/tgbaalgos/magic.hh: Adjust to
conform to the new emptiness-check interface.
* src/tgbaalgos/gtec/ce.cc, src/tgbaalgos/gtec/ce.hh,
src/tgbaalgos/gtec/gtec.cc, src/tgbaalgos/gtec/gtec.hh,
src/tgbaalgos/gtec/status.cc, src/tgbaalgos/gtec/status.hh:
Likewise.  The classes have been renamed are as following
  emptiness_check -> couvreur99_check
  emptiness_check_shy -> couvreur99_check_shy
  counter_example -> couvreur99_check_result
* src/tgbatest/ltl2tgba.cc, iface/gspn/ltlgspn.cc, iface/gspn/ssp.hh,
iface/gspn/ssp.cc: Adjust to renaming and new interface.
This commit is contained in:
Alexandre Duret-Lutz 2004-10-27 16:47:54 +00:00
parent 7010a02cd9
commit 6c815004c4
16 changed files with 523 additions and 300 deletions

View file

@ -28,15 +28,22 @@ namespace spot
namespace
{
typedef std::pair<const spot::state*, tgba_succ_iterator*> pair_state_iter;
typedef std::pair<const state*, bdd> state_proposition;
}
counter_example::counter_example(const emptiness_check_status* ecs,
const explicit_connected_component_factory*
eccf)
: ecs_(ecs)
couvreur99_check_result::couvreur99_check_result
(const couvreur99_check_status* ecs,
const explicit_connected_component_factory* eccf)
: ecs_(ecs), eccf_(eccf)
{
}
tgba_run*
couvreur99_check_result::accepting_run()
{
run_ = new tgba_run;
assert(!ecs_->root.empty());
assert(suffix.empty());
scc_stack::stack_type root = ecs_->root.s;
int comp_size = root.size();
@ -45,7 +52,7 @@ namespace spot
new explicit_connected_component*[comp_size];
for (int j = comp_size - 1; 0 <= j; --j)
{
scc[j] = eccf->build();
scc[j] = eccf_->build();
scc[j]->index = root.top().index;
scc[j]->condition = root.top().condition;
root.pop();
@ -74,7 +81,9 @@ namespace spot
numbered_state_heap::state_index_p spi =
ecs_->h->index(ecs_->aut->get_init_state());
assert(spi.first);
suffix.push_front(spi.first);
/// FIXME: Should compute label and acceptance condition.
tgba_run::step s = { spi.first, bddtrue, bddfalse };
run_->prefix.push_front(s);
// We build a path trough each SCC in the stack. For the
// first SCC, the starting state is the initial state of the
@ -93,7 +102,7 @@ namespace spot
father_map father;
// Initial state of the BFS.
const state* start = suffix.back();
const state* start = run_->prefix.back().s;
{
tgba_succ_iterator* i = ecs_->aut->succ_iter(start);
todo.push_back(pair_state_iter(start, i));
@ -114,20 +123,26 @@ namespace spot
if (!h_dest)
{
// If we have found a state in the next SCC.
// Unwind the path and populate SUFFIX.
// Unwind the path and populate RUN_->PREFIX.
h_dest = scc[k+1]->has_state(dest);
if (h_dest)
{
state_sequence seq;
tgba_run::steps seq;
seq.push_front(h_dest);
/// FIXME: Should compute label and acceptance
/// condition.
tgba_run::step s = { h_dest, bddtrue, bddfalse };
seq.push_front(s);
while (src->compare(start))
{
seq.push_front(src);
/// FIXME: Should compute label and acceptance
/// condition.
tgba_run::step s = { h_dest, bddtrue, bddfalse };
seq.push_front(s);
src = father[src];
}
// Append SEQ to SUFFIX.
suffix.splice(suffix.end(), seq);
// Append SEQ to RUN_->PREFIX.
run_->prefix.splice(run_->prefix.end(), seq);
// Exit this BFS for this SCC.
while (!todo.empty())
{
@ -152,24 +167,37 @@ namespace spot
}
}
accepting_path(scc[comp_size - 1], suffix.back(),
accepting_path(scc[comp_size - 1], run_->prefix.back().s,
scc[comp_size - 1]->condition);
run_->prefix.pop_back(); // this state belongs to the cycle.
for (int j = comp_size - 1; 0 <= j; --j)
delete scc[j];
delete[] scc;
// Clone every state in the run before returning it. (We didn't
// do that before in the algorithm, because it's easier to follow
// if every state manipulated is the instance in the hash table.)
for (tgba_run::steps::iterator i = run_->prefix.begin();
i != run_->prefix.end(); ++i)
i->s = i->s->clone();
for (tgba_run::steps::iterator i = run_->cycle.begin();
i != run_->cycle.end(); ++i)
i->s = i->s->clone();
return run_;
}
void
counter_example::complete_cycle(const explicit_connected_component* scc,
const state* from,
const state* to)
couvreur99_check_result::complete_cycle(const explicit_connected_component*
scc,
const state* from,
const state* to)
{
// If by change or period already ends on the state we have
// If by chance our cycle already ends on the state we have
// to reach back, we are done.
if (from == to
&& !period.empty())
&& !run_->cycle.empty())
return;
// Records backlinks to parent state during the BFS.
@ -207,18 +235,22 @@ namespace spot
bdd cond = i->current_condition();
// If we have reached our destination, unwind the path
// and populate PERIOD.
// and populate RUN_->CYCLE.
if (h_dest == to)
{
cycle_path p;
p.push_front(state_proposition(h_dest, cond));
tgba_run::steps p;
// FIXME: should compute acceptance condition.
tgba_run::step s = { h_dest, cond, bddfalse };
p.push_front(s);
while (src != from)
{
const state_proposition& psi = father[src];
p.push_front(state_proposition(src, psi.second));
// FIXME: should compute acceptance condition.
tgba_run::step s = { src, psi.second, bddfalse };
p.push_front(s);
src = psi.first;
}
period.splice(period.end(), p);
run_->cycle.splice(run_->cycle.end(), p);
// Exit the BFS, but release all iterators first.
while (!todo.empty())
@ -257,8 +289,10 @@ namespace spot
}
void
counter_example::accepting_path(const explicit_connected_component* scc,
const state* start, bdd acc_to_traverse)
couvreur99_check_result::accepting_path(const explicit_connected_component*
scc,
const state* start, bdd
acc_to_traverse)
{
// State seen during the DFS.
typedef Sgi::hash_set<const state*,
@ -278,9 +312,9 @@ namespace spot
}
// The path being explored currently.
cycle_path path;
tgba_run::steps path;
// The best path seen so far.
cycle_path best_path;
tgba_run::steps best_path;
// The acceptance conditions traversed by BEST_PATH.
bdd best_acc = bddfalse;
@ -314,8 +348,9 @@ namespace spot
}
bdd acc = iter->current_acceptance_conditions() | todo.top().acc;
path.push_back(state_proposition(h_dest,
iter->current_condition()));
tgba_run::step st = { h_dest, iter->current_condition(),
iter->current_acceptance_conditions() };
path.push_back(st);
// Advance iterator for next step.
iter->next();
@ -377,71 +412,30 @@ namespace spot
path.pop_back();
}
// Append our best path to the period.
for (cycle_path::iterator it = best_path.begin();
// Append our best path to the run_->cycle.
for (tgba_run::steps::iterator it = best_path.begin();
it != best_path.end(); ++it)
period.push_back(*it);
run_->cycle.push_back(*it);
// Prepare to find another path for the remaining acceptance
// conditions.
acc_to_traverse -= best_acc;
start = period.back().first;
start = run_->cycle.back().s;
}
// Complete the path so that it goes back to its beginning,
// forming a cycle.
complete_cycle(scc, start, suffix.back());
complete_cycle(scc, start, run_->prefix.back().s);
}
std::ostream&
counter_example::print_result(std::ostream& os, const tgba* restrict) const
{
os << "Prefix:" << std::endl;
const bdd_dict* d = ecs_->aut->get_dict();
for (state_sequence::const_iterator i_se = suffix.begin();
i_se != suffix.end(); ++i_se)
{
os << " ";
if (restrict)
{
const state* s = ecs_->aut->project_state(*i_se, restrict);
assert(s);
os << restrict->format_state(s) << std::endl;
delete s;
}
else
{
os << ecs_->aut->format_state(*i_se) << std::endl;
}
}
os << "Cycle:" <<std::endl;
for (cycle_path::const_iterator it = period.begin();
it != period.end(); ++it)
{
os << " | " << bdd_format_set(d, it->second) << std::endl;
os << " ";
if (restrict)
{
const state* s = ecs_->aut->project_state(it->first, restrict);
assert(s);
os << restrict->format_state(s) << std::endl;
delete s;
}
else
{
os << ecs_->aut->format_state(it->first) << std::endl;
}
}
return os;
}
void
counter_example::print_stats(std::ostream& os) const
couvreur99_check_result::print_stats(std::ostream& os) const
{
ecs_->print_stats(os);
os << suffix.size() << " states in suffix" << std::endl;
os << period.size() << " states in period" << std::endl;
// FIXME: This is bogusly assuming run_ exists. (Even if we
// created it, the user might have delete it.)
os << run_->prefix.size() << " states in run_->prefix" << std::endl;
os << run_->cycle.size() << " states in run_->cycle" << std::endl;
}
}

View file

@ -24,35 +24,26 @@
#include "status.hh"
#include "explscc.hh"
#include "tgbaalgos/emptiness.hh"
namespace spot
{
/// Compute a counter example from a spot::emptiness_check_status
class counter_example
/// Compute a counter example from a spot::couvreur99_check_status
class couvreur99_check_result: public emptiness_check_result
{
public:
counter_example(const emptiness_check_status* ecs,
const explicit_connected_component_factory*
eccf = connected_component_hash_set_factory::instance());
couvreur99_check_result(const couvreur99_check_status* ecs,
const explicit_connected_component_factory*
eccf =
connected_component_hash_set_factory::instance());
typedef std::pair<const state*, bdd> state_proposition;
typedef std::list<const state*> state_sequence;
typedef std::list<state_proposition> cycle_path;
state_sequence suffix;
cycle_path period;
/// \brief Display the example computed by counter_example().
///
/// \param os the output stream
/// \param restrict optional automaton to project the example on.
std::ostream& print_result(std::ostream& os,
const tgba* restrict = 0) const;
virtual tgba_run* accepting_run();
/// Output statistics about this object.
void print_stats(std::ostream& os) const;
protected:
/// Called by counter_example to find a path which traverses all
/// Called by couvreur99_check_result to find a path which traverses all
/// acceptance conditions in the accepted SCC.
void accepting_path (const explicit_connected_component* scc,
const state* start, bdd acc_to_traverse);
@ -63,7 +54,9 @@ namespace spot
const state* from, const state* to);
private:
const emptiness_check_status* ecs_;
const couvreur99_check_status* ecs_;
const explicit_connected_component_factory* eccf_;
tgba_run* run_;
};
}

View file

@ -20,6 +20,7 @@
// 02111-1307, USA.
#include "gtec.hh"
#include "ce.hh"
namespace spot
{
@ -28,19 +29,19 @@ namespace spot
typedef std::pair<const spot::state*, tgba_succ_iterator*> pair_state_iter;
}
emptiness_check::emptiness_check(const tgba* a,
const numbered_state_heap_factory* nshf)
couvreur99_check::couvreur99_check(const tgba* a,
const numbered_state_heap_factory* nshf)
{
ecs_ = new emptiness_check_status(a, nshf);
ecs_ = new couvreur99_check_status(a, nshf);
}
emptiness_check::~emptiness_check()
couvreur99_check::~couvreur99_check()
{
delete ecs_;
}
void
emptiness_check::remove_component(const state* from)
couvreur99_check::remove_component(const state* from)
{
// Remove from H all states which are reachable from state FROM.
@ -87,12 +88,12 @@ namespace spot
}
}
bool
emptiness_check::check()
emptiness_check_result*
couvreur99_check::check()
{
// We use five main data in this algorithm:
// * emptiness_check::root, a stack of strongly connected components (SCC),
// * emptiness_check::h, a hash of all visited nodes, with their order,
// * 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<bdd> arc;
@ -220,25 +221,25 @@ namespace spot
delete todo.top().second;
todo.pop();
}
return false;
return new couvreur99_check_result(ecs_);
}
}
// This automaton recognizes no word.
return true;
return 0;
}
const emptiness_check_status*
emptiness_check::result() const
const couvreur99_check_status*
couvreur99_check::result() const
{
return ecs_;
}
//////////////////////////////////////////////////////////////////////
emptiness_check_shy::emptiness_check_shy(const tgba* a,
const numbered_state_heap_factory*
nshf)
: emptiness_check(a, nshf), num(1)
couvreur99_check_shy::couvreur99_check_shy(const tgba* a,
const numbered_state_heap_factory*
nshf)
: couvreur99_check(a, nshf), num(1)
{
// Setup depth-first search from the initial state.
todo.push(pair_state_successors(0, succ_queue()));
@ -246,12 +247,12 @@ namespace spot
ecs_->aut->get_init_state()));
}
emptiness_check_shy::~emptiness_check_shy()
couvreur99_check_shy::~couvreur99_check_shy()
{
}
bool
emptiness_check_shy::check()
emptiness_check_result*
couvreur99_check_shy::check()
{
for (;;)
@ -336,7 +337,7 @@ namespace spot
}
todo.pop();
}
return false;
return new couvreur99_check_result(ecs_);
}
}
// Remove that state from the queue, so we do not
@ -354,7 +355,7 @@ namespace spot
todo.pop();
if (todo.empty())
// This automaton recognizes no word.
return true;
return 0;
// When backtracking the root of an SCC, we must also
// remove that SCC from the ARC/ROOT stacks. We must
@ -393,7 +394,7 @@ namespace spot
}
int*
emptiness_check_shy::find_state(const state* s)
couvreur99_check_shy::find_state(const state* s)
{
return ecs_->h->find(s).second;
}

View file

@ -23,6 +23,7 @@
# define SPOT_TGBAALGOS_GTEC_GTEC_HH
#include "status.hh"
#include "tgbaalgos/emptiness.hh"
namespace spot
{
@ -51,32 +52,32 @@ namespace spot
/// it return false, a stack of SCC has been built is available
/// using result() (spot::counter_example needs it).
///
/// There are two variants of this algorithm: spot::emptiness_check and
/// spot::emptiness_check_shy. They differ in their memory usage, the
/// 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::emptiness_check performs a straightforward depth first search.
/// spot::couvreur99_check performs a straightforward depth first search.
/// The DFS stacks store tgba_succ_iterators, so that only the
/// iterators which really are explored are computed.
///
/// spot::emptiness_check_shy tries to explore successors which are
/// 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
/// tgba_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.
class emptiness_check
class couvreur99_check: public emptiness_check
{
public:
emptiness_check(const tgba* a,
const numbered_state_heap_factory* nshf
= numbered_state_heap_hash_map_factory::instance());
virtual ~emptiness_check();
couvreur99_check(const tgba* a,
const numbered_state_heap_factory* nshf
= numbered_state_heap_hash_map_factory::instance());
virtual ~couvreur99_check();
/// Check whether the automaton's language is empty.
virtual bool check();
virtual emptiness_check_result* check();
/// \brief Return the status of the emptiness-check.
///
@ -85,11 +86,11 @@ namespace spot
///
/// This status should not be deleted, it is a pointer
/// to a member of this class that will be deleted when
/// the emptiness_check object is deleted.
const emptiness_check_status* result() const;
/// the couvreur99 object is deleted.
const couvreur99_check_status* result() const;
protected:
emptiness_check_status* ecs_;
couvreur99_check_status* ecs_;
/// \brief Remove a strongly component from the hash.
///
/// This function remove all accessible state from a given
@ -98,19 +99,19 @@ namespace spot
void remove_component(const state* start_delete);
};
/// \brief A version of spot::emptiness_check that tries to visit
/// \brief A version of spot::couvreur99_check that tries to visit
/// known states first.
///
/// See the documentation for spot::emptiness_check
class emptiness_check_shy : public emptiness_check
/// See the documentation for spot::couvreur99_check
class couvreur99_check_shy : public couvreur99_check
{
public:
emptiness_check_shy(const tgba* a,
const numbered_state_heap_factory* nshf
= numbered_state_heap_hash_map_factory::instance());
virtual ~emptiness_check_shy();
couvreur99_check_shy(const tgba* a,
const numbered_state_heap_factory* nshf
= numbered_state_heap_hash_map_factory::instance());
virtual ~couvreur99_check_shy();
virtual bool check();
virtual emptiness_check_result* check();
protected:
struct successor {
@ -120,8 +121,8 @@ namespace spot
};
// We use five main data in this algorithm:
// * emptiness_check::root, a stack of strongly connected components (SCC),
// * emptiness_check::h, a hash of all visited nodes, with their order,
// * 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<bdd> arc;

View file

@ -24,7 +24,7 @@
namespace spot
{
emptiness_check_status::emptiness_check_status
couvreur99_check_status::couvreur99_check_status
(const tgba* aut,
const numbered_state_heap_factory* nshf)
: aut(aut),
@ -32,13 +32,13 @@ namespace spot
{
}
emptiness_check_status::~emptiness_check_status()
couvreur99_check_status::~couvreur99_check_status()
{
delete h;
}
void
emptiness_check_status::print_stats(std::ostream& os) const
couvreur99_check_status::print_stats(std::ostream& os) const
{
os << h->size() << " unique states visited" << std::endl;
os << root.size()

View file

@ -34,12 +34,12 @@ namespace spot
/// 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 emptiness_check_status
class couvreur99_check_status
{
public:
emptiness_check_status(const tgba* aut,
couvreur99_check_status(const tgba* aut,
const numbered_state_heap_factory* nshf);
~emptiness_check_status();
~couvreur99_check_status();
const tgba* aut;
scc_stack root;