Vcsn  2.0
Be Rational
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
are-isomorphic.hh
Go to the documentation of this file.
1 #ifndef VCSN_ALGOS_ARE_ISOMORPHIC_HH
2 # define VCSN_ALGOS_ARE_ISOMORPHIC_HH
3 
4 # include <algorithm>
5 # include <map>
6 # include <stack>
7 # include <unordered_map>
8 # include <unordered_set>
9 
10 # include <vcsn/algos/accessible.hh>
11 # include <vcsn/dyn/automaton.hh>
12 # include <vcsn/dyn/fwd.hh>
13 # include <vcsn/misc/hash.hh>
14 # include <vcsn/misc/map.hh>
15 # include <vcsn/misc/vector.hh>
16 
17 // What we refer to as "sequentiality" in this algorithm is an obvious
18 // generalization to any context of the notion of sequentiality
19 // defined only for lal, in its turn an obvious generalization of
20 // determinism to weighted automata.
21 
22 namespace vcsn
23 {
24 
25  /*-----------------.
26  | are_isomorphic. |
27  `-----------------*/
28 
29  template <typename Aut1, typename Aut2>
31  {
32  private:
33  using automaton1_t = Aut1;
41 
42  using automaton2_t = Aut2;
50 
51  // FIXME: do we want to generalize this to heterogeneous contexts?
52  // It is not completely clear whether such behavior is desirable.
53  using weightset_t = weightset1_t; // FIXME: join!
54  using labelset_t = labelset1_t; // FIXME: join!
55 
56  using states_t = std::pair<state1_t, state2_t>;
57 
58  const Aut1& a1_;
59  const Aut2& a2_;
60 
62  template<typename Automaton>
63  using dout_t =
64  std::unordered_map
66  std::unordered_map<label_t_of<Automaton>,
67  std::pair<weight_t_of<Automaton>,
71 
75 
77  template<typename Automaton>
78  using nout_t =
79  std::unordered_map
81  std::unordered_map
83  std::unordered_map<weight_t_of<Automaton>,
84  std::vector<state_t_of<Automaton>>,
91 
95  using pair_t = std::pair<state1_t, state2_t>;
96  using worklist_t = std::stack<pair_t>;
98 
100  using s1tos2_t = std::unordered_map<state1_t, state2_t>;
101  using s2tos1_t = std::unordered_map<state2_t, state1_t>;
102 
110  {
111  enum class tag
112  {
113  never_computed = -1, // We didn't run the algorithm yet.
114  isomorphic = 0, // a1_ and a2_ are in fact isomorphic.
115  counterexample = 1, // We found a counterexample of two states
116  // [only possible for sequential automata].
117  nocounterexample = 2, // Exhaustive tests failed [we do this only
118  // for non-sequential automata].
119  trivially_different = 3, // Different number of states or transitions,
120  // of different sequentiality.
121  } response;
122 
125 
129 
131  : response(tag::never_computed)
132  {}
133  } fr_;
134 
135  // Return true and fill \a dout if \a a is sequential; otherwise
136  // return false and clear dout. We can't use the is_deterministic
137  // algorithm, as it's only defined for lal.
138  template <typename Automaton>
139  bool is_sequential_filling(const Automaton& a,
140  dout_t<Automaton>& dout)
141  {
142  for (auto t : a->all_transitions())
143  {
144  const state_t_of<Automaton>& src = a->src_of(t);
145  const label_t_of<Automaton>& l = a->label_of(t);
146  auto& doutsrc = dout[src];
147  if (doutsrc.find(l) == doutsrc.end())
148  dout[src][l] = {a->weight_of(t), a->dst_of(t)};
149  else
150  {
151  dout.clear();
152  return false;
153  }
154  }
155  return true;
156  }
157 
158  void fill_nouts_()
159  {
160  for (auto t1 : a1_->all_transitions())
161  nout1_[a1_->src_of(t1)][a1_->label_of(t1)][a1_->weight_of(t1)]
162  .emplace_back(a1_->dst_of(t1));
163  for (auto t2 : a2_->all_transitions())
164  nout2_[a2_->src_of(t2)][a2_->label_of(t2)][a2_->weight_of(t2)]
165  .emplace_back(a2_->dst_of(t2));
166  }
167 
168  void clear()
169  {
170  dout1_.clear();
171  dout2_.clear();
172  nout1_.clear();
173  nout2_.clear();
174  }
175 
177  {
178  // Automata of different sizes are different.
179  if (a1_->num_states() != a2_->num_states())
180  return true;
181  if (a1_->num_transitions() != a2_->num_transitions())
182  return true;
183 
184  // The idea of comparing alphabet sizes here is tempting, but
185  // it's more useful to only deal with the actually used labels;
186  // we consider isomorphic even two automata from labelsets with
187  // different alphabets, when the actually used labels match.
188  // Building a set of actually used labels has linear complexity,
189  // and it's not obvious that performing an additional check now
190  // would pay in real usage. FIXME: benchmark in real cases.
191 
192  return false;
193  }
194 
195  public:
196  are_isomorphicer(const Aut1 &a1, const Aut2 &a2)
197  : a1_(a1)
198  , a2_(a2)
199  {}
200 
201  const full_response
203  {
204  clear();
205 
206  // If we prove non-isomorphism at this point, it will be because
207  // of sizes.
209 
211  "are-isomorphic: lhs automaton must be accessible");
213  "are-isomorphic: rhs automaton must be accessible");
214 
215  // Before even initializing our data structures, which is
216  // potentially expensive, try to simply compare the number of
217  // elements such as states and transitions.
218  if (trivially_different())
219  return fr_;
220 
224  else
225  return fr_; // Different sequentiality.
226  else
228  return fr_; // Different sequentiality.
229  else
230  {
231  fill_nouts_();
233  }
234  }
235 
236  using states1_t = std::vector<state1_t>;
237  using states2_t = std::vector<state2_t>;
238 
243  using class_id = std::size_t;
244  using class_pair_t = std::pair<states1_t, states2_t>;
245  using state_classes_t = std::vector<class_pair_t>;
247 
248  template<typename Automaton>
250  {
251  class_id res = 0;
252 
253  std::hash_combine(res, s == a->pre());
254  std::hash_combine(res, s == a->post());
255 
256  const auto ws = * a->weightset();
257  const auto ls = * a->labelset();
258 
259  using transition_t = std::pair<weight_t_of<Automaton>,
261  using transitions_t = std::vector<transition_t>;
262  const auto less_than =
263  [&](const transition_t& t1, const transition_t& t2)
264  {
265  if (ws.less_than(t1.first, t2.first))
266  return true;
267  else if (ws.less_than(t2.first, t1.first))
268  return false;
269  else
270  return ls.less_than(t1.second, t2.second);
271  };
272 
273 #define HASH_TRANSITIONS(expression, endpoint_getter) \
274  { \
275  std::unordered_set<state_t_of<Automaton>> endpoint_states; \
276  transitions_t tt; \
277  for (auto& t: expression) \
278  { \
279  tt.emplace_back(transition_t{a->weight_of(t), a->label_of(t)}); \
280  endpoint_states.emplace(a->endpoint_getter(t)); \
281  } \
282  std::sort(tt.begin(), tt.end(), less_than); \
283  for (const auto& t: tt) \
284  { \
285  std::hash_combine(res, ws.hash(t.first)); \
286  std::hash_combine(res, ls.hash(t.second)); \
287  } \
288  std::hash_combine(res, endpoint_states.size()); \
289  }
290 
291  // Hash input transitions data, in a way which doesn't depend on
292  // state numbers or transition order.
293  HASH_TRANSITIONS(a->all_in(s), src_of);
294 
295  // Do the same for output transitions.
296  HASH_TRANSITIONS(a->all_out(s), dst_of);
297 
298 #undef HASH_TRANSITIONS
299 
300  return res;
301  }
302 
304  {
305  // Class left and right states:
306  std::unordered_map<class_id, std::pair<states1_t, states2_t>> table;
307  for (auto s1: a1_->all_states())
308  table[state_to_class(s1, a1_)].first.emplace_back(s1);
309  for (auto s2: a2_->all_states())
310  table[state_to_class(s2, a2_)].second.emplace_back(s2);
311 
312  // Return a table without class hashes sorted by decreasing
313  // (left) size, in order to perform the most constrained choices
314  // first.
315  state_classes_t res;
316  for (const auto& c: table)
317  res.emplace_back(std::move(c.second.first), std::move(c.second.second));
318  std::sort(res.begin(), res.end(),
319  [](const class_pair_t& c1, const class_pair_t& c2)
320  {
321  return c1.first.size() > c2.first.size();
322  });
323 
324  for (auto& c: res)
325  {
326  // This is mostly to make debugging easier.
327  std::sort(c.first.begin(), c.first.end());
328 
329  // This call is needed for correctness: we have to start
330  // with all classes in their "smallest" configuration in
331  // lexicographic order.
332  std::sort(c.second.begin(), c.second.end());
333  }
334 
335  //print_class_stats(res);
336  return res;
337  }
338 
341  {
342  size_t max = 0, min = a1_->num_all_states();
343  long double sum = 0.0;
344  for (const auto& c: cs)
345  {
346  max = std::max(max, c.first.size());
347  min = std::min(min, c.first.size());
348  sum += c.first.size();
349  }
350  long state_no = a1_->num_all_states();
351  std::cerr << "State no: " << state_no << "\n";
352  std::cerr << "Class no: " << cs.size() << "\n";
353  std::cerr << "* min class size: " << min << "\n";
354  std::cerr << "* avg class size: " << sum / cs.size() << "\n";
355  std::cerr << "* max class size: " << max << "\n";
356  }
357 
360  {
361  for (const auto& c: cs)
362  {
363  std::cerr << "* ";
364  for (const auto& s1: c.first)
365  std::cerr << s1 << " ";
366  std::cerr << "-- ";
367  for (const auto& s2: c.second)
368  std::cerr << s2 << " ";
369  std::cerr << "\n";
370  }
371  }
372 
374  {
375  try
376  {
378  }
379  catch (const std::out_of_range&)
380  {
381  return false;
382  }
383  }
385  {
386  std::unordered_set<state1_t> mss1;
387  std::unordered_set<state2_t> mss2;
388  std::stack<pair_t> worklist;
389  worklist.push({a1_->pre(), a2_->pre()});
390  worklist.push({a1_->post(), a2_->post()});
391  while (! worklist.empty())
392  {
393  const auto p = std::move(worklist.top()); worklist.pop();
394  const state1_t s1 = p.first;
395  const state2_t s2 = p.second;
396 
397  // Even before checking for marks, check if these two states
398  // are supposed to be isomorphic with one another. We can't
399  // avoid this just because we've visited them before.
400  if (fr_.s1tos2_.at(s1) != s2 || fr_.s2tos1_.at(s2) != s1)
401  return false;
402  const bool m1 = (mss1.find(s1) != mss1.end());
403  const bool m2 = (mss2.find(s2) != mss2.end());
404  if (m1 != m2)
405  return false;
406  else if (m1 && m2)
407  continue;
408  // Otherwise we have that ! m1 && ! m2.
409  mss1.emplace(s1);
410  mss2.emplace(s2);
411 
412  // If only one of s1 and s2 is pre or post then this is not
413  // an isomorphism.
414  if ((s1 == a1_->pre()) != (s2 == a2_->pre())
415  || (s1 == a1_->post()) != (s2 == a2_->post()))
416  return false;
417 
418  int t1n = 0, t2n = 0;
419  for (auto t1: a1_->all_out(s1))
420  {
421  auto d1 = a1_->dst_of(t1);
422  const auto& w1 = a1_->weight_of(t1);
423  const auto& l1 = a1_->label_of(t1);
424  const auto& d2s = nout2_.at(s2).at(l1).at(w1);
425  auto d2 = fr_.s1tos2_.at(d1); // according to the isomorphism
426  if (!has(d2s, d2))
427  return false;
428  worklist.push({d1, d2});
429  ++ t1n;
430  }
431  for (auto t2: a2_->all_out(s2))
432  {
433  auto d2 = a2_->dst_of(t2);
434  const auto& w2 = a2_->weight_of(t2);
435  const auto& l2 = a2_->label_of(t2);
436  const auto& d1s = nout1_.at(s1).at(l2).at(w2);
437  auto d1 = fr_.s2tos1_.at(d2); // according to the isomorphism
438  if (!has(d1s, d1))
439  return false;
440  worklist.push({d1, d2});
441  ++ t2n;
442  }
443  if (t1n != t2n)
444  return false;
445  } // while
446  return true;
447  }
448 
450  {
451  for (const auto& c: state_classes_)
452  for (int i = 0; i < int(c.first.size()); ++ i)
453  {
454  state1_t s1 = c.first[i];
455  state2_t s2 = c.second[i];
456  fr_.s1tos2_[s1] = s2;
457  fr_.s2tos1_[s2] = s1;
458  }
459  }
460 
461  long factorial(long n)
462  {
463  long res = 1;
464  for (long i = 1; i <= n; ++ i)
465  res *= i;
466  return res;
467  }
468 
471  std::vector<long> class_permutation_max_;
472  std::vector<long> class_permutation_generated_;
473 
475  {
476  class_permutation_max_.clear();
478  for (const auto& c: state_classes_) {
479  class_permutation_max_.emplace_back(factorial(c.second.size()) - 1);
480  class_permutation_generated_.emplace_back(0);
481  }
482  }
483 
484  // Build the next class combination obtained by permuting one
485  // class Like std::next_permutation, return true iff it was
486  // possible to rearrange into a "greater" configuration; if not,
487  // reset all classes to their first configuration.
489  {
490  // This algorithm is strongly reminiscent of natural number
491  // increment in a positional system, with carry propagation; in
492  // order to generate a configuration we permute the rightmost
493  // class; if this permutation resets it to its original value,
494  // we propagate the "carry" by continuing to permute the class
495  // on the left possibly propagating further left, and so on.
496 
497  const int rightmost = int(state_classes_.size()) - 1;
498 
499  // Permute the rightmost class.
500  if (std::next_permutation(state_classes_[rightmost].second.begin(),
501  state_classes_[rightmost].second.end()))
502  {
503  // Easy case: no carry.
504  ++ class_permutation_generated_[rightmost];
505  return true;
506  }
507 
508  // Permuting the rightmost class made it go back to its minimal
509  // configuration: propagate carry to the left.
510  //
511  // Carry propagation works by resetting all consecutive
512  // rightmost maximum-configuration class to their initial
513  // configuration, and permuting the leftmost one which was not
514  // maximum. If no such leftmost class exist then "the carry
515  // overflows", and we're done.
516  assert(class_permutation_generated_[rightmost]
517  == class_permutation_max_[rightmost]);
518  class_permutation_generated_[rightmost] = 0;
519  int i;
520  for (i = rightmost - 1;
521  i >= 0
523  -- i)
524  {
525  std::next_permutation(state_classes_[i].second.begin(),
526  state_classes_[i].second.end());
528  }
529  if (i == -1)
530  return false; // Carry overflow.
531 
532  // Permute in order to propagate the carry to its final place.
533  std::next_permutation(state_classes_[i].second.begin(),
534  state_classes_[i].second.end());
536  return true;
537  }
538 
539  const full_response
541  {
542  // Initialize state classes, so that later we can only do
543  // brute-force search *within* each class.
545 
546  // Don't even bother to search if the classes of left and right
547  // states have different sizes:
548  for (const auto& c: state_classes_)
549  if (c.first.size() != c.second.size())
550  {
552  return fr_;
553  }
554 
555  // Reorder the right-hand side in all possible ways allowed by
556  // classes, until we stumble on a solution.
558  do
559  {
561  if (is_isomorphism_valid())
562  {
564  return fr_;
565  }
566  }
567  while (next_class_combination());
568 
569  // We proved by exhaustion that no solution exists.
571  return fr_;
572  }
573 
574  const full_response
576  {
577  // If we prove non-isomorphism from now on, it will be by
578  // presenting some specific pair of states.
580 
581  worklist_.push({a1_->pre(), a2_->pre()});
582 
583  while (! worklist_.empty())
584  {
585  const states_t states = worklist_.top(); worklist_.pop();
586  const state1_t s1 = states.first;
587  const state2_t s2 = states.second;
588 
589  // If we prove non-isomorphism in this iteration, it will be
590  // by using the <s1, s2> pair as a counterexample.
591  fr_.counterexample = {s1, s2};
592 
593  // If the number of transitions going out from the two
594  // states is different, don't even bother looking at them.
595  // On the other hand if the number is equal we can afford to
596  // reason in just one direction: look at transitions from s1
597  // and ensure that all of them have a matching one from s2.
598  if (dout1_[s1].size() != dout2_[s2].size())
599  return fr_;
600 
601  for (const auto& l1_w1dst1 : dout1_[s1]) // dout1_.at(s1) may fail.
602  {
603  const label1_t& l1 = l1_w1dst1.first;
604  const weight1_t& w1 = l1_w1dst1.second.first;
605  const state1_t& dst1 = l1_w1dst1.second.second;
606 
607  const auto& s2out = dout2_.find(s2);
608  if (s2out == dout2_.cend())
609  return fr_;
610  const auto& s2outl = s2out->second.find(l1);
611  if (s2outl == s2out->second.cend())
612  return fr_;
613  weight2_t w2 = s2outl->second.first;
614  state2_t dst2 = s2outl->second.second;
615 
616  if (! weightset_t::equals(w1, w2))
617  return fr_;
618 
619  const auto& isomorphics_to_dst1 = fr_.s1tos2_.find(dst1);
620  const auto& isomorphics_to_dst2 = fr_.s2tos1_.find(dst2);
621 
622  if (isomorphics_to_dst1 == fr_.s1tos2_.cend())
623  {
624  if (isomorphics_to_dst2 == fr_.s2tos1_.cend()) // Both empty.
625  {
626  fr_.s1tos2_[dst1] = dst2;
627  fr_.s2tos1_[dst2] = dst1;
628  worklist_.push({dst1, dst2});
629  }
630  else
631  return fr_;
632  }
633  else if (isomorphics_to_dst1 == fr_.s1tos2_.cend()
634  || isomorphics_to_dst1->second != dst2
635  || isomorphics_to_dst2->second != dst1)
636  return fr_;
637  } // outer for
638  } // while
640  return fr_;
641  }
642 
643  bool operator()()
644  {
645  const full_response& r = get_full_response();
647  }
648 
652  using origins_t = std::map<state2_t, state1_t>;
653 
655  origins_t
657  {
659  __func__, ": isomorphism-check not successfully performed");
660  origins_t res;
661  for (const auto& s2s1: fr_.s2tos1_)
662  res[s2s1.first] = s2s1.second;
663  return res;
664  }
665 
667  static
668  std::ostream&
669  print(const origins_t& orig, std::ostream& o)
670  {
671  o << "/* Origins." << std::endl
672  << " node [shape = box, style = rounded]" << std::endl;
673  for (auto p : orig)
674  if (2 <= p.first)
675  {
676  o << " " << p.first - 2
677  << " [label = \"" << p.second << "\"]" << std::endl;
678  }
679  o << "*/" << std::endl;
680  return o;
681  }
682 
683  };
684 
685  template <typename Aut1, typename Aut2>
686  bool
687  are_isomorphic(const Aut1& a1, const Aut2& a2)
688  {
690 
691  return are_isomorphic();
692  }
693 
694  namespace dyn
695  {
696  namespace detail
697  {
698 
700  template <typename Aut1, typename Aut2>
701  bool
702  are_isomorphic(const automaton& aut1, const automaton& aut2)
703  {
704  const auto& a1 = aut1->as<Aut1>();
705  const auto& a2 = aut2->as<Aut2>();
706  return are_isomorphic(a1, a2);
707  }
708 
710  (const automaton&, const automaton&) -> bool);
711  }
712  }
713 }
714 
715 #endif // !VCSN_ALGOS_ARE_ISOMORPHIC_HH
void print_classes(const state_classes_t &cs)
Handy debugging method.
std::vector< long > class_permutation_generated_
struct vcsn::are_isomorphicer::full_response fr_
void print_class_stats(const state_classes_t &cs)
Handy debugging method.
REGISTER_DECLARE(accessible,(const automaton &) -> automaton)
std::shared_ptr< detail::automaton_base > automaton
Definition: automaton.hh:71
dout_t< automaton1_t > dout1_
For the simpler, faster sequential case.
labelset_t_of< context1_t > labelset1_t
std::pair< state1_t, state2_t > pair_t
A worklist of pairs of states which are candidate to be isomorphic.
std::stack< pair_t > worklist_t
std::unordered_map< state_t_of< Automaton >, std::unordered_map< label_t_of< Automaton >, std::pair< weight_t_of< Automaton >, state_t_of< Automaton >>, vcsn::hash< labelset_t_of< Automaton >>, vcsn::equal_to< labelset_t_of< Automaton >>>> dout_t
See the comment for out_ in minimize.hh.
std::pair< states1_t, states2_t > class_pair_t
typename detail::labelset_t_of_impl< base_t< ValueSet >>::type labelset_t_of
Definition: traits.hh:34
#define HASH_TRANSITIONS(expression, endpoint_getter)
transition_t_of< automaton2_t > transition2_t
context_t_of< automaton2_t > context2_t
std::map< state2_t, state1_t > origins_t
A map from each a2_ state to the corresponding a1_ state.
void initialize_next_class_combination_state()
transition_t_of< automaton1_t > transition1_t
class_id state_to_class(state_t_of< Automaton > s, Automaton &a)
bool are_isomorphic(const Aut1 &a1, const Aut2 &a2)
static std::ostream & print(const origins_t &orig, std::ostream &o)
Print origins.
weightset_t_of< automaton1_t > weightset1_t
const full_response get_full_response_sequential()
std::vector< state1_t > states1_t
typename detail::context_t_of_impl< base_t< ValueSet >>::type context_t_of
Definition: traits.hh:32
weight_t_of< automaton2_t > weight2_t
std::vector< state2_t > states2_t
label_t_of< automaton1_t > label1_t
const full_response get_full_response_nonsequential()
auto sort(const Aut &a) -> permutation_automaton< Aut >
Definition: sort.hh:185
state_t_of< automaton1_t > state1_t
typename detail::label_t_of_impl< base_t< ValueSet >>::type label_t_of
Definition: traits.hh:33
typename detail::weightset_t_of_impl< base_t< ValueSet >>::type weightset_t_of
Definition: traits.hh:38
state_t_of< automaton2_t > state2_t
bool is_sequential_filling(const Automaton &a, dout_t< Automaton > &dout)
weightset_t_of< automaton1_t > weightset2_t
bool are_isomorphic(const automaton &aut1, const automaton &aut2)
Bridge.
std::unordered_map< state_t_of< Automaton >, std::unordered_map< label_t_of< Automaton >, std::unordered_map< weight_t_of< Automaton >, std::vector< state_t_of< Automaton >>, vcsn::hash< weightset_t_of< Automaton >>, vcsn::equal_to< weightset_t_of< Automaton >>>, vcsn::hash< labelset_t_of< Automaton >>, vcsn::equal_to< labelset_t_of< Automaton >>>> nout_t
For the nonsequential case.
typename detail::transition_t_of_impl< base_t< ValueSet >>::type transition_t_of
Definition: traits.hh:36
std::vector< long > class_permutation_max_
We need to keep some (small) state between a next_class_combination call and the next.
dout_t< automaton2_t > dout2_
enum vcsn::are_isomorphicer::full_response::tag response
Provide a variadic mul on top of a binary mul(), and one().
Definition: fwd.hh:36
std::vector< std::pair< string_t, string_t >> transitions_t
Definition: parse.hh:74
auto sum(const A &lhs, const B &rhs) -> decltype(join_automata(lhs, rhs))
Definition: sum.hh:65
are_isomorphicer(const Aut1 &a1, const Aut2 &a2)
label_t_of< automaton2_t > label2_t
typename detail::weight_t_of_impl< base_t< ValueSet >>::type weight_t_of
Definition: traits.hh:37
origins_t origins()
Only meaningful if operator() returned true.
context_t_of< automaton1_t > context1_t
std::size_t class_id
Automaton states partitioned into classes.
state_classes_t state_classes_
bool is_accessible(const Aut &a)
Definition: accessible.hh:163
nout_t< automaton2_t > nout2_
s1tos2_t s1tos2_
Only meaningful if the tag is tag::isomorphic.
This is useful to make hashes with labels or weights as keys without using non-default constructors; ...
Definition: hash.hh:42
typename detail::state_t_of_impl< base_t< ValueSet >>::type state_t_of
Definition: traits.hh:35
const state_classes_t make_state_classes()
RatExpSet::value_t less_than(const RatExpSet &rs, const typename RatExpSet::value_t &v)
Definition: less-than.hh:150
std::vector< class_pair_t > state_classes_t
labelset_t_of< context2_t > labelset2_t
std::pair< state1_t, state2_t > states_t
nout_t< automaton1_t > nout1_
A datum specifying if two given automata are isomorphic, and why if they are not. ...
std::unordered_map< state1_t, state2_t > s1tos2_t
The maps associating the states of a1_ and the states of a2_->
weight_t_of< automaton1_t > weight1_t
pair_t counterexample
Only meaningful if the tag is tag::counterexample.
const full_response get_full_response()
bool has(const std::map< Key, Value, Compare, Alloc > &s, const Key &e)
Definition: map.hh:35
void require(bool b, Args &&...args)
If b is not verified, raise an error with args as message.
Definition: raise.hh:39
This is useful to make hashes with labels or weights as keys without using non-default constructors; ...
Definition: hash.hh:26
std::unordered_map< state2_t, state1_t > s2tos1_t