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