tgbagraph: add a merge_transitions() method.
* src/graph/graph.hh: Add some framework to erase transitions, and defrag the resulting transitions_ vector on demand. Also remove the nb_states() and nb_transitions() because num_states() and num_transitions() already exist. * src/graphtest/graph.cc, src/graphtest/ngraph.cc: Adjust to use num_states(). * src/tgba/tgbagraph.hh (merge_transitions): New method. * src/misc/hash.hh: Add a pair_hash class, needed by merge_transitions(). * src/graphtest/tgbagraph.cc, src/graphtest/tgbagraph.test: Add states for transitions removal and merge_transitions().
This commit is contained in:
parent
9909699c63
commit
424de90385
7 changed files with 286 additions and 10 deletions
|
|
@ -239,11 +239,80 @@ namespace spot
|
||||||
return t_;
|
return t_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transition trans() const
|
||||||
|
{
|
||||||
|
return t_;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Graph* g_;
|
Graph* g_;
|
||||||
transition t_;
|
transition t_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename Graph>
|
||||||
|
class SPOT_API killer_trans_iterator: public trans_iterator<Graph>
|
||||||
|
{
|
||||||
|
typedef trans_iterator<Graph> super;
|
||||||
|
public:
|
||||||
|
typedef typename Graph::state_storage_t state_storage_t;
|
||||||
|
typedef typename Graph::transition transition;
|
||||||
|
|
||||||
|
killer_trans_iterator(Graph* g, transition t, state_storage_t& src):
|
||||||
|
super(g, t), src_(src), prev_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
killer_trans_iterator operator++()
|
||||||
|
{
|
||||||
|
prev_ = this->t_;
|
||||||
|
this->t_ = this->operator*().next_succ;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
killer_trans_iterator operator++(int)
|
||||||
|
{
|
||||||
|
killer_trans_iterator ti = *this;
|
||||||
|
prev_ = this->t_;
|
||||||
|
this->t_ = this->operator*().next_succ;
|
||||||
|
return ti;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase the current transition and advance the iterator.
|
||||||
|
void erase()
|
||||||
|
{
|
||||||
|
transition next = this->operator*().next_succ;
|
||||||
|
|
||||||
|
// Update source state and previous transitions
|
||||||
|
if (prev_)
|
||||||
|
{
|
||||||
|
this->g_->trans_storage(prev_).next_succ = next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (src_.succ == this->t_)
|
||||||
|
src_.succ = next;
|
||||||
|
}
|
||||||
|
if (src_.succ_tail == this->t_)
|
||||||
|
{
|
||||||
|
src_.succ_tail = prev_;
|
||||||
|
assert(next == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erased transitions have themselves as next_succ.
|
||||||
|
this->operator*().next_succ = this->t_;
|
||||||
|
|
||||||
|
// Advance iterator to next transitions.
|
||||||
|
this->t_ = next;
|
||||||
|
|
||||||
|
++this->g_->killed_trans_;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
state_storage_t& src_;
|
||||||
|
transition prev_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// State OUT
|
// State OUT
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
|
|
@ -289,6 +358,7 @@ namespace spot
|
||||||
{
|
{
|
||||||
friend class internal::trans_iterator<digraph>;
|
friend class internal::trans_iterator<digraph>;
|
||||||
friend class internal::trans_iterator<const digraph>;
|
friend class internal::trans_iterator<const digraph>;
|
||||||
|
friend class internal::killer_trans_iterator<digraph>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef internal::trans_iterator<digraph> iterator;
|
typedef internal::trans_iterator<digraph> iterator;
|
||||||
|
|
@ -325,7 +395,8 @@ namespace spot
|
||||||
protected:
|
protected:
|
||||||
state_vector states_;
|
state_vector states_;
|
||||||
trans_vector transitions_;
|
trans_vector transitions_;
|
||||||
|
// Number of erased transitions.
|
||||||
|
unsigned killed_trans_;
|
||||||
public:
|
public:
|
||||||
/// \brief construct an empty graph
|
/// \brief construct an empty graph
|
||||||
///
|
///
|
||||||
|
|
@ -334,6 +405,7 @@ namespace spot
|
||||||
/// limits, but just hints to pre-allocate a data structure that
|
/// limits, but just hints to pre-allocate a data structure that
|
||||||
/// may hold that much items.
|
/// may hold that much items.
|
||||||
digraph(unsigned max_states = 10, unsigned max_trans = 0)
|
digraph(unsigned max_states = 10, unsigned max_trans = 0)
|
||||||
|
: killed_trans_(0)
|
||||||
{
|
{
|
||||||
states_.reserve(max_states);
|
states_.reserve(max_states);
|
||||||
if (max_trans == 0)
|
if (max_trans == 0)
|
||||||
|
|
@ -351,7 +423,15 @@ namespace spot
|
||||||
|
|
||||||
unsigned num_transitions() const
|
unsigned num_transitions() const
|
||||||
{
|
{
|
||||||
return transitions_.size();
|
return transitions_.size() - killed_trans_ - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valid_trans(transition t) const
|
||||||
|
{
|
||||||
|
// Erased transitions have their next_succ pointing to
|
||||||
|
// themselves.
|
||||||
|
return (t < transitions_.size() &&
|
||||||
|
transitions_[t].next_succ != t);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
|
|
@ -449,26 +529,52 @@ namespace spot
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state index_of_state(state_storage_t& ss)
|
||||||
|
{
|
||||||
|
assert(!states_.empty());
|
||||||
|
return &ss - &states_.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
transition index_of_transition(trans_storage_t& tt)
|
||||||
|
{
|
||||||
|
assert(!transitions_.empty());
|
||||||
|
return &tt - &transitions_.front();
|
||||||
|
}
|
||||||
|
|
||||||
internal::state_out<digraph>
|
internal::state_out<digraph>
|
||||||
out(state src)
|
out(state src)
|
||||||
{
|
{
|
||||||
return {this, states_[src].succ};
|
return {this, states_[src].succ};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal::state_out<digraph>
|
||||||
|
out(state_storage_t& src)
|
||||||
|
{
|
||||||
|
return out(index_of_state(src));
|
||||||
|
}
|
||||||
|
|
||||||
internal::state_out<const digraph>
|
internal::state_out<const digraph>
|
||||||
out(state src) const
|
out(state src) const
|
||||||
{
|
{
|
||||||
return {this, states_[src].succ};
|
return {this, states_[src].succ};
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned nb_states() const
|
internal::state_out<const digraph>
|
||||||
|
out(state_storage_t& src) const
|
||||||
{
|
{
|
||||||
return states_.size();
|
return out(index_of_state(src));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned nb_trans() const
|
internal::killer_trans_iterator<digraph>
|
||||||
|
out_iteraser(state_storage_t& src)
|
||||||
{
|
{
|
||||||
return transitions_.size();
|
return {this, src.succ, src};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal::killer_trans_iterator<digraph>
|
||||||
|
out_iteraser(state src)
|
||||||
|
{
|
||||||
|
return out_iteraser(state_storage(src));
|
||||||
}
|
}
|
||||||
|
|
||||||
const state_vector& states() const
|
const state_vector& states() const
|
||||||
|
|
@ -481,6 +587,42 @@ namespace spot
|
||||||
return states_;
|
return states_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void defrag()
|
||||||
|
{
|
||||||
|
if (killed_trans_ == 0) // Nothing to do.
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Shift all transitions in transitions_. The algorithm is
|
||||||
|
// similar to remove_if, but it also keeps the correspondence
|
||||||
|
// between the old and new index as newidx[old] = new.
|
||||||
|
unsigned tend = transitions_.size();
|
||||||
|
std::vector<transition> newidx(tend);
|
||||||
|
unsigned dest = 1;
|
||||||
|
for (transition t = 1; t < tend; ++t)
|
||||||
|
{
|
||||||
|
if (transitions_[t].next_succ == t)
|
||||||
|
continue;
|
||||||
|
if (t != dest)
|
||||||
|
transitions_[dest] = std::move(transitions_[t]);
|
||||||
|
newidx[t] = dest;
|
||||||
|
++dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions_.resize(dest);
|
||||||
|
killed_trans_ = 0;
|
||||||
|
|
||||||
|
// Adjust next_succ pointers in all transitions.
|
||||||
|
for (transition t = 1; t < dest; ++t)
|
||||||
|
transitions_[t].next_succ = newidx[transitions_[t].next_succ];
|
||||||
|
|
||||||
|
// Adjust succ and succ_tails pointers in all states.
|
||||||
|
for (auto& s: states_)
|
||||||
|
{
|
||||||
|
s.succ = newidx[s.succ];
|
||||||
|
s.succ_tail = newidx[s.succ_tail];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ void
|
||||||
dot(std::ostream& out, spot::digraph<SL, TL>& g)
|
dot(std::ostream& out, spot::digraph<SL, TL>& g)
|
||||||
{
|
{
|
||||||
out << "digraph {\n";
|
out << "digraph {\n";
|
||||||
unsigned c = g.nb_states();
|
unsigned c = g.num_states();
|
||||||
for (unsigned s = 0; s < c; ++s)
|
for (unsigned s = 0; s < c; ++s)
|
||||||
{
|
{
|
||||||
out << ' ' << s;
|
out << ' ' << s;
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ void
|
||||||
dot(std::ostream& out, const spot::digraph<SL, TL>& g)
|
dot(std::ostream& out, const spot::digraph<SL, TL>& g)
|
||||||
{
|
{
|
||||||
out << "digraph {\n";
|
out << "digraph {\n";
|
||||||
unsigned c = g.nb_states();
|
unsigned c = g.num_states();
|
||||||
for (unsigned s = 0; s < c; ++s)
|
for (unsigned s = 0; s < c; ++s)
|
||||||
{
|
{
|
||||||
out << ' ' << s;
|
out << ' ' << s;
|
||||||
|
|
@ -93,7 +93,7 @@ dot(std::ostream& out, const spot::named_graph<G1, G2, G3, G4>& g)
|
||||||
{
|
{
|
||||||
out << "digraph {\n";
|
out << "digraph {\n";
|
||||||
auto& gg = g.graph();
|
auto& gg = g.graph();
|
||||||
unsigned c = gg.nb_states();
|
unsigned c = gg.num_states();
|
||||||
for (unsigned s = 0; s < c; ++s)
|
for (unsigned s = 0; s < c; ++s)
|
||||||
{
|
{
|
||||||
out << ' ' << s;
|
out << ' ' << s;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ void f1()
|
||||||
auto s1 = g.new_state();
|
auto s1 = g.new_state();
|
||||||
auto s2 = g.new_state();
|
auto s2 = g.new_state();
|
||||||
auto s3 = g.new_state();
|
auto s3 = g.new_state();
|
||||||
|
g.new_transition(s1, s1, bddfalse, bddfalse);
|
||||||
g.new_transition(s1, s2, p1, bddfalse);
|
g.new_transition(s1, s2, p1, bddfalse);
|
||||||
g.new_transition(s1, s3, p2, !a1 & a2);
|
g.new_transition(s1, s3, p2, !a1 & a2);
|
||||||
g.new_transition(s2, s3, p1 & p2, a1 & !a2);
|
g.new_transition(s2, s3, p1 & p2, a1 & !a2);
|
||||||
|
|
@ -52,6 +53,36 @@ void f1()
|
||||||
g.new_transition(s3, s3, bddtrue, (!a1 & a2) | (a1 & !a2));
|
g.new_transition(s3, s3, bddtrue, (!a1 & a2) | (a1 & !a2));
|
||||||
|
|
||||||
spot::dotty_reachable(std::cout, &tg);
|
spot::dotty_reachable(std::cout, &tg);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto i = g.out_iteraser(s3);
|
||||||
|
++i;
|
||||||
|
i.erase();
|
||||||
|
i.erase();
|
||||||
|
assert(!i);
|
||||||
|
spot::dotty_reachable(std::cout, &tg);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto i = g.out_iteraser(s3);
|
||||||
|
i.erase();
|
||||||
|
assert(!i);
|
||||||
|
spot::dotty_reachable(std::cout, &tg);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.new_transition(s3, s1, p1 | p2, (!a1 & a2) | (a1 & !a2));
|
||||||
|
g.new_transition(s3, s2, p1 >> p2, bddfalse);
|
||||||
|
g.new_transition(s3, s1, bddtrue, (!a1 & a2) | (a1 & !a2));
|
||||||
|
|
||||||
|
std::cerr << tg.num_transitions() << '\n';
|
||||||
|
assert(tg.num_transitions() == 7);
|
||||||
|
|
||||||
|
spot::dotty_reachable(std::cout, &tg);
|
||||||
|
tg.merge_transitions();
|
||||||
|
spot::dotty_reachable(std::cout, &tg);
|
||||||
|
|
||||||
|
std::cerr << tg.num_transitions() << '\n';
|
||||||
|
assert(tg.num_transitions() == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ digraph G {
|
||||||
0 [label="", style=invis, height=0]
|
0 [label="", style=invis, height=0]
|
||||||
0 -> 1
|
0 -> 1
|
||||||
1 [label="0"]
|
1 [label="0"]
|
||||||
|
1 -> 1 [label="0\n"]
|
||||||
1 -> 2 [label="p1\n"]
|
1 -> 2 [label="p1\n"]
|
||||||
1 -> 3 [label="p2\n{Acc[p2]}"]
|
1 -> 3 [label="p2\n{Acc[p2]}"]
|
||||||
2 [label="1"]
|
2 [label="1"]
|
||||||
|
|
@ -46,6 +47,55 @@ digraph G {
|
||||||
3 -> 2 [label="!p1 | p2\n"]
|
3 -> 2 [label="!p1 | p2\n"]
|
||||||
3 -> 3 [label="1\n{Acc[p2], Acc[p1]}"]
|
3 -> 3 [label="1\n{Acc[p2], Acc[p1]}"]
|
||||||
}
|
}
|
||||||
|
digraph G {
|
||||||
|
0 [label="", style=invis, height=0]
|
||||||
|
0 -> 1
|
||||||
|
1 [label="0"]
|
||||||
|
1 -> 1 [label="0\n"]
|
||||||
|
1 -> 2 [label="p1\n"]
|
||||||
|
1 -> 3 [label="p2\n{Acc[p2]}"]
|
||||||
|
2 [label="1"]
|
||||||
|
2 -> 3 [label="p1 & p2\n{Acc[p1]}"]
|
||||||
|
3 [label="2"]
|
||||||
|
3 -> 1 [label="p1 | p2\n{Acc[p2], Acc[p1]}"]
|
||||||
|
}
|
||||||
|
digraph G {
|
||||||
|
0 [label="", style=invis, height=0]
|
||||||
|
0 -> 1
|
||||||
|
1 [label="0"]
|
||||||
|
1 -> 1 [label="0\n"]
|
||||||
|
1 -> 2 [label="p1\n"]
|
||||||
|
1 -> 3 [label="p2\n{Acc[p2]}"]
|
||||||
|
2 [label="1"]
|
||||||
|
2 -> 3 [label="p1 & p2\n{Acc[p1]}"]
|
||||||
|
3 [label="2"]
|
||||||
|
}
|
||||||
|
digraph G {
|
||||||
|
0 [label="", style=invis, height=0]
|
||||||
|
0 -> 1
|
||||||
|
1 [label="0"]
|
||||||
|
1 -> 1 [label="0\n"]
|
||||||
|
1 -> 2 [label="p1\n"]
|
||||||
|
1 -> 3 [label="p2\n{Acc[p2]}"]
|
||||||
|
2 [label="1"]
|
||||||
|
2 -> 3 [label="p1 & p2\n{Acc[p1]}"]
|
||||||
|
3 [label="2"]
|
||||||
|
3 -> 1 [label="p1 | p2\n{Acc[p2], Acc[p1]}"]
|
||||||
|
3 -> 2 [label="!p1 | p2\n"]
|
||||||
|
3 -> 1 [label="1\n{Acc[p2], Acc[p1]}"]
|
||||||
|
}
|
||||||
|
digraph G {
|
||||||
|
0 [label="", style=invis, height=0]
|
||||||
|
0 -> 1
|
||||||
|
1 [label="0"]
|
||||||
|
1 -> 2 [label="p1\n"]
|
||||||
|
1 -> 3 [label="p2\n{Acc[p2]}"]
|
||||||
|
2 [label="1"]
|
||||||
|
2 -> 3 [label="p1 & p2\n{Acc[p1]}"]
|
||||||
|
3 [label="2"]
|
||||||
|
3 -> 1 [label="1\n{Acc[p2], Acc[p1]}"]
|
||||||
|
3 -> 2 [label="!p1 | p2\n"]
|
||||||
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
diff stdout expected
|
diff stdout expected
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,18 @@ namespace spot
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct pair_hash
|
||||||
|
{
|
||||||
|
template<typename T, typename U>
|
||||||
|
std::size_t operator()(const std::pair<T, U> &p) const
|
||||||
|
{
|
||||||
|
return wang32_hash(static_cast<size_t>(p.first) ^
|
||||||
|
static_cast<size_t>(p.second));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // SPOT_MISC_HASH_HH
|
#endif // SPOT_MISC_HASH_HH
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ namespace spot
|
||||||
{
|
{
|
||||||
auto s = down_cast<const typename graph_t::state_storage_t*>(st);
|
auto s = down_cast<const typename graph_t::state_storage_t*>(st);
|
||||||
assert(s);
|
assert(s);
|
||||||
assert(s->succ < g_.num_transitions());
|
assert(!s->succ || g_.valid_trans(s->succ));
|
||||||
|
|
||||||
if (this->iter_cache_)
|
if (this->iter_cache_)
|
||||||
{
|
{
|
||||||
|
|
@ -308,6 +308,47 @@ namespace spot
|
||||||
{
|
{
|
||||||
return bddtrue;
|
return bddtrue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate over all transitions, and merge those with compatible
|
||||||
|
/// extremities and acceptance.
|
||||||
|
void merge_transitions()
|
||||||
|
{
|
||||||
|
for (auto& s: g_.states())
|
||||||
|
{
|
||||||
|
// Map a pair (dest state, acc) to the first transition seen
|
||||||
|
// with such characteristic.
|
||||||
|
|
||||||
|
typedef std::pair<graph_t::state, int> key_t;
|
||||||
|
std::unordered_map<key_t, graph_t::transition, pair_hash> trmap;
|
||||||
|
|
||||||
|
auto t = g_.out_iteraser(s);
|
||||||
|
while (t)
|
||||||
|
{
|
||||||
|
// Simply skip false transitions.
|
||||||
|
if (t->cond == bddfalse)
|
||||||
|
{
|
||||||
|
t.erase();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_t k(t->dst, t->acc.id());
|
||||||
|
auto p = trmap.insert(make_pair(k, t.trans()));
|
||||||
|
if (!p.second)
|
||||||
|
{
|
||||||
|
// A previous transitions exist for k, merge the
|
||||||
|
// condition, and schedule the transition for
|
||||||
|
// removal.
|
||||||
|
g_.trans_data(p.first->second).cond |= t->cond;
|
||||||
|
t.erase();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_.defrag();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue