Vcsn  2.1
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 <typename Aut1, typename 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<typename Automaton>
65  using dout_t =
68  std::unordered_map<label_t_of<Automaton>,
69  std::pair<weight_t_of<Automaton>,
70  state_t_of<Automaton>>,
73 
77 
79  template<typename Automaton>
80  using nout_t =
82  <state_t_of<Automaton>,
85  std::unordered_map<weight_t_of<Automaton>,
86  std::vector<state_t_of<Automaton>>,
89  vcsn::hash<labelset_t_of<Automaton>>,
93 
97  using pair_t = std::pair<state1_t, state2_t>;
98  using worklist_t = std::stack<pair_t>;
100 
102  using s1tos2_t = std::unordered_map<state1_t, state2_t>;
103  using s2tos1_t = std::unordered_map<state2_t, state1_t>;
104 
112  {
113  enum class tag
114  {
115  never_computed = -1, // We didn't run the algorithm yet.
116  isomorphic = 0, // a1_ and a2_ are in fact isomorphic.
117  counterexample = 1, // We found a counterexample of two states
118  // [only possible for sequential automata].
119  nocounterexample = 2, // Exhaustive tests failed [we do this only
120  // for non-sequential automata].
121  trivially_different = 3, // Different number of states or transitions,
122  // of different sequentiality.
123  } response;
124 
127 
131 
133  : response(tag::never_computed)
134  {}
135  } fr_;
136 
137  // Return true and fill \a dout if \a a is sequential; otherwise
138  // return false and clear dout. We can't use the is_deterministic
139  // algorithm, as it's only defined for lal.
140  template <typename Automaton>
141  bool is_sequential_filling(const Automaton& a,
142  dout_t<Automaton>& dout)
143  {
144  for (auto t : a->all_transitions())
145  {
146  const state_t_of<Automaton>& src = a->src_of(t);
147  const label_t_of<Automaton>& l = a->label_of(t);
148  auto& doutsrc = dout[src];
149  if (doutsrc.find(l) == doutsrc.end())
150  dout[src][l] = {a->weight_of(t), a->dst_of(t)};
151  else
152  {
153  dout.clear();
154  return false;
155  }
156  }
157  return true;
158  }
159 
160  void fill_nouts_()
161  {
162  for (auto t1 : a1_->all_transitions())
163  nout1_[a1_->src_of(t1)][a1_->label_of(t1)][a1_->weight_of(t1)]
164  .emplace_back(a1_->dst_of(t1));
165  for (auto t2 : a2_->all_transitions())
166  nout2_[a2_->src_of(t2)][a2_->label_of(t2)][a2_->weight_of(t2)]
167  .emplace_back(a2_->dst_of(t2));
168  }
169 
170  void clear()
171  {
172  dout1_.clear();
173  dout2_.clear();
174  nout1_.clear();
175  nout2_.clear();
176  }
177 
179  {
180  // Automata of different sizes are different.
181  if (a1_->num_states() != a2_->num_states())
182  return true;
183  if (a1_->num_transitions() != a2_->num_transitions())
184  return true;
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 
194  return false;
195  }
196 
197  public:
198  are_isomorphicer(const Aut1 &a1, const Aut2 &a2)
199  : a1_(a1)
200  , a2_(a2)
201  {}
202 
203  const full_response
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<typename Automaton>
251  class_id state_to_class(state_t_of<Automaton> s, Automaton& 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<Automaton>,
262  label_t_of<Automaton>>;
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  std::unordered_set<state_t_of<Automaton>> endpoint_states; \
278  transitions_t tt; \
279  for (auto& t: expression) \
280  { \
281  tt.emplace_back(transition_t{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(a->all_in(s), src_of);
296 
297  // Do the same for output transitions.
298  HASH_TRANSITIONS(a->all_out(s), dst_of);
299 
300 #undef HASH_TRANSITIONS
301 
302  return res;
303  }
304 
306  {
307  // Class left and right states:
308  std::unordered_map<class_id, std::pair<states1_t, states2_t>> table;
309  for (auto s1: a1_->all_states())
310  table[state_to_class(s1, a1_)].first.emplace_back(s1);
311  for (auto s2: a2_->all_states())
312  table[state_to_class(s2, a2_)].second.emplace_back(s2);
313 
314  // Return a table without class hashes sorted by decreasing
315  // (left) size, in order to perform the most constrained choices
316  // first.
317  state_classes_t res;
318  for (const auto& c: table)
319  res.emplace_back(std::move(c.second.first), std::move(c.second.second));
320  boost::sort(res,
321  [](const class_pair_t& c1, const class_pair_t& c2)
322  {
323  return c1.first.size() > c2.first.size();
324  });
325 
326  for (auto& c: res)
327  {
328  // This is mostly to make debugging easier.
329  boost::sort(c.first);
330 
331  // This call is needed for correctness: we have to start
332  // with all classes in their "smallest" configuration in
333  // lexicographic order.
334  boost::sort(c.second);
335  }
336 
337  //print_class_stats(res);
338  return res;
339  }
340 
343  {
344  size_t max = 0, min = a1_->num_all_states();
345  long double sum = 0.0;
346  for (const auto& c: cs)
347  {
348  max = std::max(max, c.first.size());
349  min = std::min(min, c.first.size());
350  sum += c.first.size();
351  }
352  long state_no = a1_->num_all_states();
353  std::cerr << "State no: " << state_no << "\n";
354  std::cerr << "Class no: " << cs.size() << "\n";
355  std::cerr << "* min class size: " << min << "\n";
356  std::cerr << "* avg class size: " << sum / cs.size() << "\n";
357  std::cerr << "* max class size: " << max << "\n";
358  }
359 
362  {
363  for (const auto& c: cs)
364  {
365  std::cerr << "* ";
366  for (const auto& s1: c.first)
367  std::cerr << s1 << " ";
368  std::cerr << "-- ";
369  for (const auto& s2: c.second)
370  std::cerr << s2 << " ";
371  std::cerr << "\n";
372  }
373  }
374 
376  {
377  try
378  {
380  }
381  catch (const std::out_of_range&)
382  {
383  return false;
384  }
385  }
387  {
388  std::unordered_set<state1_t> mss1;
389  std::unordered_set<state2_t> mss2;
390  std::stack<pair_t> worklist;
391  worklist.push({a1_->pre(), a2_->pre()});
392  worklist.push({a1_->post(), a2_->post()});
393  while (! worklist.empty())
394  {
395  const auto p = std::move(worklist.top()); worklist.pop();
396  const state1_t s1 = p.first;
397  const state2_t s2 = p.second;
398 
399  // Even before checking for marks, check if these two states
400  // are supposed to be isomorphic with one another. We can't
401  // avoid this just because we've visited them before.
402  if (fr_.s1tos2_.at(s1) != s2 || fr_.s2tos1_.at(s2) != s1)
403  return false;
404  const bool m1 = (mss1.find(s1) != mss1.end());
405  const bool m2 = (mss2.find(s2) != mss2.end());
406  if (m1 != m2)
407  return false;
408  else if (m1 && m2)
409  continue;
410  // Otherwise we have that ! m1 && ! m2.
411  mss1.emplace(s1);
412  mss2.emplace(s2);
413 
414  // If only one of s1 and s2 is pre or post then this is not
415  // an isomorphism.
416  if ((s1 == a1_->pre()) != (s2 == a2_->pre())
417  || (s1 == a1_->post()) != (s2 == a2_->post()))
418  return false;
419 
420  int t1n = 0, t2n = 0;
421  for (auto t1: a1_->all_out(s1))
422  {
423  auto d1 = a1_->dst_of(t1);
424  const auto& w1 = a1_->weight_of(t1);
425  const auto& l1 = a1_->label_of(t1);
426  const auto& d2s = nout2_.at(s2).at(l1).at(w1);
427  auto d2 = fr_.s1tos2_.at(d1); // according to the isomorphism
428  if (!has(d2s, d2))
429  return false;
430  worklist.push({d1, d2});
431  ++ t1n;
432  }
433  for (auto t2: a2_->all_out(s2))
434  {
435  auto d2 = a2_->dst_of(t2);
436  const auto& w2 = a2_->weight_of(t2);
437  const auto& l2 = a2_->label_of(t2);
438  const auto& d1s = nout1_.at(s1).at(l2).at(w2);
439  auto d1 = fr_.s2tos1_.at(d2); // according to the isomorphism
440  if (!has(d1s, d1))
441  return false;
442  worklist.push({d1, d2});
443  ++ t2n;
444  }
445  if (t1n != t2n)
446  return false;
447  } // while
448  return true;
449  }
450 
452  {
453  for (const auto& c: state_classes_)
454  for (int i = 0; i < int(c.first.size()); ++ i)
455  {
456  state1_t s1 = c.first[i];
457  state2_t s2 = c.second[i];
458  fr_.s1tos2_[s1] = s2;
459  fr_.s2tos1_[s2] = s1;
460  }
461  }
462 
463  long factorial(long n)
464  {
465  long res = 1;
466  for (long i = 1; i <= n; ++ i)
467  res *= i;
468  return res;
469  }
470 
473  std::vector<long> class_permutation_max_;
474  std::vector<long> class_permutation_generated_;
475 
477  {
478  class_permutation_max_.clear();
479  class_permutation_generated_.clear();
480  for (const auto& c: state_classes_) {
481  class_permutation_max_.emplace_back(factorial(c.second.size()) - 1);
482  class_permutation_generated_.emplace_back(0);
483  }
484  }
485 
486  // Build the next class combination obtained by permuting one
487  // class Like std::next_permutation, return true iff it was
488  // possible to rearrange into a "greater" configuration; if not,
489  // reset all classes to their first configuration.
491  {
492  // This algorithm is strongly reminiscent of natural number
493  // increment in a positional system, with carry propagation; in
494  // order to generate a configuration we permute the rightmost
495  // class; if this permutation resets it to its original value,
496  // we propagate the "carry" by continuing to permute the class
497  // on the left possibly propagating further left, and so on.
498 
499  const int rightmost = int(state_classes_.size()) - 1;
500 
501  // Permute the rightmost class.
502  if (boost::next_permutation(state_classes_[rightmost].second))
503  {
504  // Easy case: no carry.
505  ++ class_permutation_generated_[rightmost];
506  return true;
507  }
508 
509  // Permuting the rightmost class made it go back to its minimal
510  // configuration: propagate carry to the left.
511  //
512  // Carry propagation works by resetting all consecutive
513  // rightmost maximum-configuration class to their initial
514  // configuration, and permuting the leftmost one which was not
515  // maximum. If no such leftmost class exist then "the carry
516  // overflows", and we're done.
517  assert(class_permutation_generated_[rightmost]
518  == class_permutation_max_[rightmost]);
519  class_permutation_generated_[rightmost] = 0;
520  int i;
521  for (i = rightmost - 1;
522  i >= 0
523  && class_permutation_generated_[i] == class_permutation_max_[i];
524  -- i)
525  {
526  boost::next_permutation(state_classes_[i].second);
527  class_permutation_generated_[i] = 0;
528  }
529  if (i == -1)
530  return false; // Carry overflow.
531 
532  // Permute in order to propagate the carry to its final place.
533  boost::next_permutation(state_classes_[i].second);
534  ++ class_permutation_generated_[i];
535  return true;
536  }
537 
538  const full_response
540  {
541  // Initialize state classes, so that later we can only do
542  // brute-force search *within* each class.
543  state_classes_ = make_state_classes();
544 
545  // Don't even bother to search if the classes of left and right
546  // states have different sizes:
547  for (const auto& c: state_classes_)
548  if (c.first.size() != c.second.size())
549  {
551  return fr_;
552  }
553 
554  // Reorder the right-hand side in all possible ways allowed by
555  // classes, until we stumble on a solution.
557  do
558  {
560  if (is_isomorphism_valid())
561  {
563  return fr_;
564  }
565  }
566  while (next_class_combination());
567 
568  // We proved by exhaustion that no solution exists.
570  return fr_;
571  }
572 
573  const full_response
575  {
576  // If we prove non-isomorphism from now on, it will be by
577  // presenting some specific pair of states.
579 
580  worklist_.push({a1_->pre(), a2_->pre()});
581 
582  while (! worklist_.empty())
583  {
584  const states_t states = worklist_.top(); worklist_.pop();
585  const state1_t s1 = states.first;
586  const state2_t s2 = states.second;
587 
588  // If we prove non-isomorphism in this iteration, it will be
589  // by using the <s1, s2> pair as a counterexample.
590  fr_.counterexample = {s1, s2};
591 
592  // If the number of transitions going out from the two
593  // states is different, don't even bother looking at them.
594  // On the other hand if the number is equal we can afford to
595  // reason in just one direction: look at transitions from s1
596  // and ensure that all of them have a matching one from s2.
597  if (dout1_[s1].size() != dout2_[s2].size())
598  return fr_;
599 
600  for (const auto& l1_w1dst1 : dout1_[s1]) // dout1_.at(s1) may fail.
601  {
602  const label1_t& l1 = l1_w1dst1.first;
603  const weight1_t& w1 = l1_w1dst1.second.first;
604  const state1_t& dst1 = l1_w1dst1.second.second;
605 
606  const auto& s2out = dout2_.find(s2);
607  if (s2out == dout2_.cend())
608  return fr_;
609  const auto& s2outl = s2out->second.find(l1);
610  if (s2outl == s2out->second.cend())
611  return fr_;
612  weight2_t w2 = s2outl->second.first;
613  state2_t dst2 = s2outl->second.second;
614 
615  if (! weightset_t::equal(w1, w2))
616  return fr_;
617 
618  const auto& isomorphics_to_dst1 = fr_.s1tos2_.find(dst1);
619  const auto& isomorphics_to_dst2 = fr_.s2tos1_.find(dst2);
620 
621  if (isomorphics_to_dst1 == fr_.s1tos2_.cend())
622  {
623  if (isomorphics_to_dst2 == fr_.s2tos1_.cend()) // Both empty.
624  {
625  fr_.s1tos2_[dst1] = dst2;
626  fr_.s2tos1_[dst2] = dst1;
627  worklist_.push({dst1, dst2});
628  }
629  else
630  return fr_;
631  }
632  else if (isomorphics_to_dst1 == fr_.s1tos2_.cend()
633  || isomorphics_to_dst1->second != dst2
634  || isomorphics_to_dst2->second != dst1)
635  return fr_;
636  } // outer for
637  } // while
639  return fr_;
640  }
641 
642  bool operator()()
643  {
644  const full_response& r = get_full_response();
646  }
647 
651  using origins_t = std::map<state2_t, state1_t>;
652 
654  origins_t
656  {
658  __func__, ": isomorphism-check not successfully performed");
659  origins_t res;
660  for (const auto& s2s1: fr_.s2tos1_)
661  res[s2s1.first] = s2s1.second;
662  return res;
663  }
664 
666  static
667  std::ostream&
668  print(const origins_t& orig, std::ostream& o)
669  {
670  o << "/* Origins." << std::endl
671  << " node [shape = box, style = rounded]" << std::endl;
672  for (auto p : orig)
673  if (2 <= p.first)
674  {
675  o << " " << p.first - 2
676  << " [label = \"" << p.second << "\"]" << std::endl;
677  }
678  o << "*/" << std::endl;
679  return o;
680  }
681 
682  };
683 
684  template <typename Aut1, typename Aut2>
685  bool
686  are_isomorphic(const Aut1& a1, const Aut2& a2)
687  {
689 
690  return are_isomorphic();
691  }
692 
693  namespace dyn
694  {
695  namespace detail
696  {
697 
699  template <typename Aut1, typename Aut2>
700  bool
701  are_isomorphic(const automaton& aut1, const automaton& aut2)
702  {
703  const auto& a1 = aut1->as<Aut1>();
704  const auto& a2 = aut2->as<Aut2>();
705  return are_isomorphic(a1, a2);
706  }
707  }
708  }
709 }
std::vector< state1_t > states1_t
dout_t< automaton2_t > dout2_
s1tos2_t s1tos2_
Only meaningful if the tag is tag::isomorphic.
std::vector< state2_t > states2_t
bool is_accessible(const Aut &a)
Whether all its states are accessible.
Definition: accessible.hh:175
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
typename detail::label_t_of_impl< base_t< ValueSet >>::type label_t_of
Definition: traits.hh:46
std::size_t class_id
Automaton states partitioned into classes.
Functor to compare Values of ValueSets.
Definition: functional.hh:72
state_t_of< automaton1_t > state1_t
state_classes_t state_classes_
nout_t< automaton2_t > nout2_
typename detail::state_t_of_impl< base_t< ValueSet >>::type state_t_of
Definition: traits.hh:48
typename detail::context_t_of_impl< base_t< ValueSet >>::type context_t_of
Definition: traits.hh:45
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.
std::vector< class_pair_t > state_classes_t
typename detail::weightset_t_of_impl< base_t< ValueSet >>::type weightset_t_of
Definition: traits.hh:51
std::pair< state1_t, state2_t > states_t
std::unordered_map< state1_t, state2_t > s1tos2_t
The maps associating the states of a1_ and the states of a2_->
bool are_isomorphic(const automaton &aut1, const automaton &aut2)
Bridge.
const full_response get_full_response()
std::unordered_map< state2_t, state1_t > s2tos1_t
are_isomorphicer(const Aut1 &a1, const Aut2 &a2)
label_t_of< automaton2_t > label2_t
origins_t origins()
Only meaningful if operator() returned true.
enum vcsn::are_isomorphicer::full_response::tag response
context_t_of< automaton1_t > context1_t
void hash_combine(std::size_t &seed, const T &v)
Definition: functional.hh:32
struct vcsn::are_isomorphicer::full_response fr_
#define HASH_TRANSITIONS(expression, endpoint_getter)
This is useful to make hashes with labels or weights as keys without using non-default constructors; ...
Definition: functional.hh:42
std::vector< std::pair< string_t, string_t >> transitions_t
Definition: parse.hh:72
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.
transition_t_of< automaton2_t > transition2_t
Provide a variadic mul on top of a binary mul(), and one().
Definition: fwd.hh:46
const state_classes_t make_state_classes()
pair_t counterexample
Only meaningful if the tag is tag::counterexample.
context_t_of< automaton2_t > context2_t
labelset_t_of< context2_t > labelset2_t
bool are_isomorphic(const Aut1 &a1, const Aut2 &a2)
std::shared_ptr< detail::automaton_base > automaton
Definition: automaton.hh:69
size_t size(const ExpSet &rs, const typename ExpSet::value_t &r)
typename detail::labelset_t_of_impl< base_t< ValueSet >>::type labelset_t_of
Definition: traits.hh:47
nout_t< automaton1_t > nout1_
std::map< state2_t, state1_t > origins_t
A map from each a2_ state to the corresponding a1_ state.
weight_t_of< automaton1_t > weight1_t
void require(bool b, Args &&...args)
If b is not verified, raise an error with args as message.
Definition: raise.hh:75
void initialize_next_class_combination_state()
transition_t_of< automaton1_t > transition1_t
void print_classes(const state_classes_t &cs)
Handy debugging method.
typename detail::transition_t_of_impl< base_t< ValueSet >>::type transition_t_of
Definition: traits.hh:49
std::vector< long > class_permutation_generated_
static std::ostream & print(const origins_t &orig, std::ostream &o)
Print origins.
weightset_t_of< automaton1_t > weightset1_t
A datum specifying if two given automata are isomorphic, and why if they are not. ...
const full_response get_full_response_sequential()
void print_class_stats(const state_classes_t &cs)
Handy debugging method.
auto sum(const A &lhs, const B &rhs) -> decltype(join_automata(lhs, rhs))
Definition: sum.hh:64
weight_t_of< automaton2_t > weight2_t
std::pair< states1_t, states2_t > class_pair_t
ATTRIBUTE_PURE bool has(const std::deque< T, Allocator > &s, const T &e)
Whether e is member of s.
Definition: deque.hh:13
const full_response get_full_response_nonsequential()
class_id state_to_class(state_t_of< Automaton > s, Automaton &a)
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
auto sort(const Aut &a) -> permutation_automaton< Aut >
Definition: sort.hh:163
std::vector< long > class_permutation_max_
We need to keep some (small) state between a next_class_combination call and the next.
typename detail::weight_t_of_impl< base_t< ValueSet >>::type weight_t_of
Definition: traits.hh:50