Vcsn  2.1
Be Rational
synchronizing-word.hh
Go to the documentation of this file.
1 #pragma once
2 
3 #include <algorithm>
4 #include <iostream>
5 #include <limits>
6 #include <unordered_map>
7 #include <unordered_set>
8 #include <utility>
9 #include <vector>
10 
11 #include <boost/algorithm/string.hpp>
12 
13 #include <vcsn/algos/distance.hh>
14 #include <vcsn/algos/pair.hh>
15 #include <vcsn/ctx/context.hh>
16 #include <vcsn/ctx/traits.hh>
17 #include <vcsn/dyn/automaton.hh>
18 #include <vcsn/dyn/context.hh>
19 #include <vcsn/dyn/label.hh>
20 #include <vcsn/labelset/labelset.hh> // make_wordset
21 #include <vcsn/misc/algorithm.hh> // erase_if
22 #include <vcsn/misc/map.hh>
23 #include <vcsn/misc/pair.hh>
24 #include <vcsn/misc/raise.hh>
25 
26 namespace vcsn
27 {
28 
29  /*--------------------------------------.
30  | is_synchronized_by(automaton, word). |
31  `--------------------------------------*/
32 
34  template <typename Aut>
35  bool
36  is_synchronized_by(const Aut& aut, const word_t_of<Aut>& w)
37  {
38  using automaton_t = Aut;
39  using state_t = state_t_of<automaton_t>;
40 
41  std::unordered_set<state_t> todo;
42 
43  for (auto s : aut->states())
44  todo.insert(s);
45 
46  for (auto l : aut->labelset()->letters_of(w))
47  {
48  std::unordered_set<state_t> new_todo;
49  for (auto s : todo)
50  {
51  auto ntf = aut->out(s, l);
52  auto size = ntf.size();
53  require(0 < size,
54  "is_synchronized_by: automaton must be complete");
55  require(size < 2,
56  "is_synchronized_by: automaton must be deterministic");
57  new_todo.insert(aut->dst_of(*ntf.begin()));
58  }
59  todo = std::move(new_todo);
60  }
61 
62  return todo.size() == 1;
63  }
64 
65  namespace dyn
66  {
67  namespace detail
68  {
70  template <typename Aut, typename LabelSet>
71  bool
72  is_synchronized_by(const automaton& aut, const label& word)
73  {
74  const auto& a = aut->as<Aut>();
75  const auto& w = word->as<LabelSet>();
76  return vcsn::is_synchronized_by(a, w.label());
77  }
78  }
79  }
80 
81 
82  /*---------------------.
83  | word_synchronizer. |
84  `---------------------*/
85 
86  namespace detail
87  {
88  template <typename Aut>
90  {
91  public:
92  using automaton_t = Aut;
97 
98  private:
99  using pair_t = std::pair<state_t, state_t>;
100  using dist_transition_t = std::pair<unsigned, transition_t>;
101  using paths_t = std::unordered_map<state_t, dist_transition_t>;
102  using path_t = typename paths_t::value_type;
103 
107  std::unordered_set<state_t> todo_;
109 
110  public:
112  : aut_(aut)
113  {}
114 
115  private:
116  void init_pair(bool keep_initials = false)
117  {
118  pair_ = pair(aut_, keep_initials);
119  paths_ = paths_ibfs(pair_, pair_->singletons());
120 
121  if (keep_initials)
122  detail::erase_if(paths_,
123  [this](const path_t& p)
124  {
125  return pair_->is_singleton(p.first);
126  });
127  }
128 
129  void init_synchro(bool keep_initials = false)
130  {
131  init_pair(keep_initials);
132  require(pair_->states().size()
133  == paths_.size() + pair_->singletons().size(),
134  "automaton is not synchronizing");
135 
136  for (auto s : pair_->states())
137  if (!pair_->is_singleton(s))
138  todo_.insert(s);
139  }
140 
141  std::vector<transition_t> recompose_path(state_t from) const
142  {
143  std::vector<transition_t> res;
144  state_t bt_curr = from;
145  while (!pair_->is_singleton(bt_curr))
146  {
147  transition_t t = paths_.find(bt_curr)->second.second;
148  res.push_back(t);
149  bt_curr = pair_->dst_of(t);
150  }
151  return res;
152  }
153 
154  int dist(state_t s) const
155  {
156  return pair_->is_singleton(s) ? 0 : paths_.find(s)->second.first;
157  }
158 
159  state_t dest_state(state_t s, const label_t& l) const
160  {
161  auto ntf = pair_->out(s, l);
162  auto size = ntf.size();
163  require(0 < size, "automaton must be complete");
164  require(size < 2, "automaton must be deterministic");
165  return pair_->dst_of(*ntf.begin());
166  }
167 
168  void apply_label(const label_t& label)
169  {
170  res_ = aut_->labelset()->mul(res_, label);
171  std::unordered_set<state_t> new_todo;
172  for (auto s : todo_)
173  {
174  auto new_state = dest_state(s, label);
175  if (!pair_->is_singleton(new_state))
176  new_todo.insert(new_state);
177  }
178  todo_ = std::move(new_todo);
179  }
180 
183  void apply_path(const std::vector<transition_t>& path)
184  {
185  for (auto t : path)
186  apply_label(pair_->label_of(t));
187  }
188 
189  public:
190 
191  // We just perform an inverse BFS from q0 and put all the accessible
192  // states in 'paths'. If the size of paths is the same than the number
193  // of states of pa (minus q0), then for each pair of states (p, q),
194  // there is a word w such that d(p, w) = d(q, w), thus the automaton is
195  // synchronizing.
197  {
198  init_pair();
199  return paths_.size() == pair_->states().size() - 1;
200  }
201 
203  {
205  }
206 
208  {
209  return cycle_();
210  }
211 
213  {
215  }
216 
218  {
220  }
221 
223  {
224  return fastsynchro_();
225  }
226 
227  private:
228  using heuristic_t = auto (word_synchronizer::*)(state_t) const -> int;
229 
231  {
232  init_synchro();
233  while (!todo_.empty())
234  {
235  int min = std::numeric_limits<int>::max();
236  state_t s_min = 0;
237  for (auto s : todo_)
238  {
239  int d = (this->*(heuristic))(s);
240  if (d < min)
241  {
242  min = d;
243  s_min = s;
244  }
245  }
246 
247  apply_path(recompose_path(s_min));
248  }
249  return res_;
250  }
251 
253  {
254  init_synchro(true);
255  bool first = true;
256  state_t previous = 0;
257  while (!todo_.empty())
258  {
259  int min = std::numeric_limits<int>::max();
260  state_t s_min = 0;
261  for (auto s : todo_)
262  {
263  pair_t o = pair_->get_origin(s);
264  if (!first && o.first != previous && o.second != previous)
265  continue;
266  int d = dist(s);
267  if (d < min)
268  {
269  min = d;
270  s_min = s;
271  }
272  }
273 
274  const auto& path = recompose_path(s_min);
275  pair_t pair_end = pair_->get_origin(
276  pair_->dst_of(path[path.size() - 1]));
277  assert(pair_end.first == pair_end.second);
278  previous = pair_end.first;
279  first = false;
280  apply_path(path);
281  }
282  return res_;
283  }
284 
286  {
287  init_synchro();
288 
289  // The drawback of this algorithm is that it does not guarantee us to
290  // converge, so we this to counter prevent potential infinite loops.
291  unsigned count = 0;
292  while (!todo_.empty())
293  {
294  // compute lmin = arg min { phi_3(l) } forall l in labelset
295  label_t lmin;
296  int min = std::numeric_limits<int>::max();
297  for (const auto& l : pair_->labelset()->genset())
298  {
299  int cur_min = phi_3(l);
300  if (cur_min < min)
301  {
302  min = cur_min;
303  lmin = l;
304  }
305  }
306 
307  unsigned sq_bound = aut_->states().size();
308  if (min < 0 && count++ < (sq_bound * sq_bound))
309  apply_label(lmin);
310  else
311  {
312  // fallback on the phi_2 heuristic, with a size restriction.
313  int count = 0;
314  size_t t = todo_.size();
315  int bound = std::min(aut_->states().size(), (t * t - t / 2));
316  int min = std::numeric_limits<int>::max();
317  state_t s_min = 0;
318  for (auto s : todo_)
319  {
320  if (count++ >= bound)
321  break;
322  int d = phi_2(s);
323  if (d < min)
324  {
325  min = d;
326  s_min = s;
327  }
328  }
329  apply_path(recompose_path(s_min));
330  }
331  }
332  return res_;
333  }
334 
336  int delta(state_t p, const std::vector<transition_t>& w) const
337  {
338  state_t np = p;
339  for (auto t : w)
340  np = dest_state(np, pair_->label_of(t));
341  return dist(np) - dist(p);
342  }
343 
347  int phi_1(state_t p) const
348  {
349  int res = 0;
350  auto w = recompose_path(p);
351  for (auto s: todo_)
352  if (s != p)
353  res += delta(s, w);
354  return res;
355  }
356 
359  int phi_2(state_t p) const
360  {
361  return phi_1(p) + dist(p);
362  }
363 
366  int phi_3(const label_t& l) const
367  {
368  int res = 0;
369  for (auto s: todo_)
370  res += dist(dest_state(s, l)) - dist(s);
371  return res;
372  }
373  };
374  }
375 
376 
377  /*-----------------------------.
378  | is_synchronizing(automaton). |
379  `-----------------------------*/
380 
382  template <typename Aut>
383  bool is_synchronizing(const Aut& aut)
384  {
386  return sync.is_synchronizing();
387  }
388 
389  namespace dyn
390  {
391  namespace detail
392  {
394  template <typename Aut>
395  bool is_synchronizing(const automaton& aut)
396  {
397  const auto& a = aut->as<Aut>();
398  return vcsn::is_synchronizing(a);
399  }
400  }
401  }
402 
403 
404  /*-------------------------------.
405  | synchronizing_word(automaton). |
406  `-------------------------------*/
407 
409  template <typename Aut>
410  word_t_of<Aut>
411  synchronizing_word(const Aut& aut, const std::string& algo = "greedy")
412  {
414  if (boost::iequals(algo, "greedy") || boost::iequals(algo, "eppstein"))
415  return sync.greedy();
416  else if (boost::iequals(algo, "cycle"))
417  return sync.cycle();
418  else if (boost::iequals(algo, "synchrop"))
419  return sync.synchroP();
420  else if (boost::iequals(algo, "synchropl"))
421  return sync.synchroPL();
422  else if (boost::iequals(algo, "fastsynchro"))
423  return sync.fastsynchro();
424  else
425  raise("synchronizing_word: invalid algorithm: ", str_escape(algo));
426  }
427 
428  namespace dyn
429  {
430  namespace detail
431  {
433  template <typename Aut, typename String>
434  label
435  synchronizing_word(const automaton& aut, const std::string& algo)
436  {
437  const auto& a = aut->as<Aut>();
438  auto word = vcsn::synchronizing_word(a, algo);
439  return make_label(make_wordset(*a->labelset()), word);
440  }
441  }
442  }
443 }
label synchronizing_word(const automaton &aut, const std::string &algo)
Bridge.
std::ostream & str_escape(std::ostream &os, const std::string &str)
Output a string, escaping special characters.
Definition: escape.cc:43
typename detail::label_t_of_impl< base_t< ValueSet >>::type label_t_of
Definition: traits.hh:46
std::shared_ptr< const detail::label_base > label
Definition: fwd.hh:59
bool is_synchronizing(const automaton &aut)
Bridge.
word_t_of< automaton_t > word_t
std::unordered_map< state_t, dist_transition_t > paths_t
typename detail::state_t_of_impl< base_t< ValueSet >>::type state_t_of
Definition: traits.hh:48
label_t_of< automaton_t > label_t
word_t_of< Aut > synchronizing_word(const Aut &aut, const std::string &algo="greedy")
Return a synchronizing word for aut using algo algo.
std::shared_ptr< detail::pair_automaton_impl< Aut >> pair_automaton
Definition: pair.hh:236
std::pair< state_t, state_t > pair_t
bool is_synchronizing(const Aut &aut)
Whether this automaton is synchronizing, i.e., has synchronizing words.
std::unordered_map< state_t_of< Aut >, std::pair< unsigned, transition_t_of< Aut > > > paths_ibfs(const Aut &aut, const std::vector< state_t_of< Aut >> &start)
Find the shortest paths from some states to all the states.
Definition: distance.hh:190
std::vector< transition_t > recompose_path(state_t from) const
transition_t_of< automaton_t > transition_t
typename labelset_t_of< base_t< ValueSet >>::word_t word_t_of
Definition: traits.hh:65
int phi_3(const label_t &l) const
Heuristic used for FastSynchro.
word_synchronizer(const automaton_t &aut)
void erase_if(Container &c, const Predicate &p)
Definition: algorithm.hh:40
void apply_label(const label_t &label)
std::shared_ptr< detail::automaton_base > automaton
Definition: automaton.hh:69
state_t dest_state(state_t s, const label_t &l) const
law_t< LabelSet > make_wordset(const LabelSet &ls)
The wordset of a labelset.
Definition: labelset.hh:261
size_t size(const ExpSet &rs, const typename ExpSet::value_t &r)
int delta(state_t p, const std::vector< transition_t > &w) const
Compute dist(d(s, w)) - dist(s).
void require(bool b, Args &&...args)
If b is not verified, raise an error with args as message.
Definition: raise.hh:75
word_t synchro(heuristic_t heuristic)
typename detail::transition_t_of_impl< base_t< ValueSet >>::type transition_t_of
Definition: traits.hh:49
auto(word_synchronizer::*)(state_t) const -> int heuristic_t
int phi_1(state_t p) const
Heuristic used for SynchroP.
void apply_path(const std::vector< transition_t > &path)
"Apply" a word to the set of active states (for each state, for each label, perform s = d(s)) ...
void init_pair(bool keep_initials=false)
pair_automaton< Aut > pair(const Aut &aut, bool keep_initials=false)
Definition: pair.hh:243
void init_synchro(bool keep_initials=false)
bool is_synchronized_by(const automaton &aut, const label &word)
Bridge.
std::unordered_set< state_t > todo_
int phi_2(state_t p) const
Heuristic used for SynchroPL.
state_t_of< automaton_t > state_t
std::pair< unsigned, transition_t > dist_transition_t
Paths in filesystems, i.e., file names.
Definition: path.hh:21
typename paths_t::value_type path_t
bool is_synchronized_by(const Aut &aut, const word_t_of< Aut > &w)
Whether w synchronizes automaton aut.
label make_label(const LabelSet &ls, const typename LabelSet::value_t &l)
Definition: label.hh:80