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