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