Adding function to canonicalize an automaton.
* src/tgbaalgos/are_isomorphic.cc, src/tgbaalgos/are_isomorphic.hh, src/bin/autfilt.cc: are_isomorphic now uses canonicalize. It returns a bool, because the mapping cannot be deduced easily from the canonicalized automaton. * src/graph/graph.hh: Add equality operator to trans_storage_t for easy comparison of transition vectors. * src/tgba/tgbagraph.hh: Add equality operator to tgba_graph_trans_data and to tgba_digraph. * src/tgbaalgos/canonicalize.cc, src/tgbaalgos/canonicalize.hh: New files. * src/tgbaalgos/Makefile.am: Add them. * src/tgbatest/isomorph.test: Test them.
This commit is contained in:
parent
b83d6d7f29
commit
1995602df5
9 changed files with 271 additions and 187 deletions
|
|
@ -22,182 +22,18 @@
|
|||
|
||||
#include "tgba/tgbagraph.hh"
|
||||
#include "tgbaalgos/are_isomorphic.hh"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include "misc/hashfunc.hh"
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef size_t class_t;
|
||||
typedef std::vector<unsigned> states_t;
|
||||
typedef std::unordered_map<class_t, states_t> class2states_t;
|
||||
typedef std::vector<class_t> state2class_t;
|
||||
|
||||
struct transition
|
||||
{
|
||||
unsigned src;
|
||||
unsigned dst;
|
||||
int cond;
|
||||
spot::acc_cond::mark_t acc;
|
||||
|
||||
|
||||
transition(unsigned src, unsigned dst, int cond, spot::acc_cond::mark_t acc)
|
||||
: src(src), dst(dst), cond(cond), acc(acc)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const transition& o) const
|
||||
{
|
||||
return src == o.src && dst == o.dst && cond == o.cond && acc == o.acc;
|
||||
}
|
||||
};
|
||||
|
||||
static bool
|
||||
trans_lessthan(const transition& ts1, const transition& ts2)
|
||||
{
|
||||
return
|
||||
ts1.src != ts2.src ?
|
||||
ts1.src < ts2.src :
|
||||
ts1.dst != ts2.dst ?
|
||||
ts1.dst < ts2.dst :
|
||||
ts1.acc != ts2.acc ?
|
||||
ts1.acc < ts2.acc :
|
||||
ts1.cond < ts2.cond;
|
||||
}
|
||||
|
||||
static state2class_t
|
||||
map_state_class(const spot::const_tgba_digraph_ptr& a)
|
||||
{
|
||||
state2class_t hashin(a->num_states(), 0);
|
||||
state2class_t hashout(a->num_states(), 0);
|
||||
state2class_t state2class(a->num_states());
|
||||
|
||||
for (auto& t: a->transitions())
|
||||
{
|
||||
hashout[t.src] ^= spot::wang32_hash(t.cond.id());
|
||||
hashout[t.src] ^= spot::wang32_hash(t.acc);
|
||||
hashin[t.dst] ^= spot::wang32_hash(t.cond.id());
|
||||
hashin[t.dst] ^= spot::wang32_hash(t.acc);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < a->num_states(); ++i)
|
||||
// Rehash the ingoing transitions so that the total hash differs for
|
||||
// different (in, out) pairs of ingoing and outgoing transitions, even if
|
||||
// the union of in and out is the same.
|
||||
state2class[i] = spot::wang32_hash(hashin[i]) ^ hashout[i];
|
||||
|
||||
// XOR the initial state's hash with a pseudo random value so that it is
|
||||
// in its own class.
|
||||
state2class[a->get_init_state_number()] ^= 2654435761U;
|
||||
return state2class;
|
||||
}
|
||||
|
||||
static class2states_t
|
||||
map_class_states(const spot::const_tgba_digraph_ptr& a)
|
||||
{
|
||||
unsigned n = a->num_states();
|
||||
std::vector<states_t> res;
|
||||
class2states_t class2states;
|
||||
auto state2class = map_state_class(a);
|
||||
|
||||
for (unsigned s = 0; s < n; ++s)
|
||||
{
|
||||
class_t c1 = state2class[s];
|
||||
auto& states =
|
||||
class2states.emplace(c1, std::vector<unsigned>()).first->second;
|
||||
states.emplace_back(s);
|
||||
}
|
||||
|
||||
return class2states;
|
||||
}
|
||||
|
||||
static bool
|
||||
mapping_from_classes(std::vector<unsigned>& mapping,
|
||||
class2states_t classes1,
|
||||
class2states_t classes2)
|
||||
{
|
||||
if (classes1.size() != classes2.size())
|
||||
return false;
|
||||
for (auto& p : classes1)
|
||||
{
|
||||
auto& c2 = classes2[p.first];
|
||||
if (p.second.size() != c2.size())
|
||||
return false;
|
||||
auto ps = p.second.size();
|
||||
for (unsigned j = 0; j < ps; ++j)
|
||||
mapping[p.second[j]] = c2[j];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
next_class_permutation(class2states_t& classes)
|
||||
{
|
||||
for (auto& p : classes)
|
||||
if (std::next_permutation(p.second.begin(), p.second.end()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
are_trivially_different(const spot::const_tgba_digraph_ptr a1,
|
||||
const spot::const_tgba_digraph_ptr a2)
|
||||
{
|
||||
return (a1->num_states() != a2->num_states()
|
||||
|| a1->num_transitions() != a2->num_transitions()
|
||||
|| a1->acc().num_sets() != a2->acc().num_sets());
|
||||
}
|
||||
}
|
||||
#include "tgbaalgos/canonicalize.hh"
|
||||
|
||||
namespace spot
|
||||
{
|
||||
std::vector<unsigned>
|
||||
are_isomorphic(const const_tgba_digraph_ptr a1,
|
||||
const const_tgba_digraph_ptr a2)
|
||||
bool
|
||||
are_isomorphic(const const_tgba_digraph_ptr aut1,
|
||||
const const_tgba_digraph_ptr aut2)
|
||||
{
|
||||
if (are_trivially_different(a1, a2))
|
||||
return {};
|
||||
unsigned n = a1->num_states();
|
||||
assert(n == a2->num_states());
|
||||
class2states_t a1_classes = map_class_states(a1);
|
||||
class2states_t a2_classes = map_class_states(a2);
|
||||
std::vector<unsigned> mapping(n);
|
||||
|
||||
// Get the first possible mapping between a1 and a2, or return false if
|
||||
// the number of class or the size of the classes do not match.
|
||||
if (!(mapping_from_classes(mapping, a1_classes, a2_classes)))
|
||||
return {};
|
||||
|
||||
unsigned tend = a1->num_transitions();
|
||||
assert(tend == a2->num_transitions());
|
||||
|
||||
// a2 is our reference automaton. Keep a sorted list of its
|
||||
// transition for easy comparison.
|
||||
std::vector<transition> trans2;
|
||||
trans2.reserve(tend);
|
||||
for (auto& t: a2->transitions())
|
||||
trans2.emplace_back(t.src, t.dst, t.acc, t.cond.id());
|
||||
std::sort(trans2.begin(), trans2.end(), trans_lessthan);
|
||||
|
||||
std::vector<transition> trans1;
|
||||
trans1.reserve(tend);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Check if current mapping matches the reference automaton
|
||||
trans1.clear();
|
||||
for (auto& t: a1->transitions())
|
||||
trans1.emplace_back(mapping[t.src], mapping[t.dst],
|
||||
t.acc, t.cond.id());
|
||||
std::sort(trans1.begin(), trans1.end(), trans_lessthan);
|
||||
if (trans1 == trans2)
|
||||
return mapping;
|
||||
|
||||
// Consider next mapping
|
||||
if (!next_class_permutation(a2_classes))
|
||||
return {};
|
||||
mapping_from_classes(mapping, a1_classes, a2_classes);
|
||||
}
|
||||
auto tmp1 = make_tgba_digraph(aut1);
|
||||
auto tmp2 = make_tgba_digraph(aut2);
|
||||
spot::canonicalize(tmp1);
|
||||
spot::canonicalize(tmp2);
|
||||
return *tmp1 == *tmp2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue