Vcsn  2.4
Be Rational
random-automaton.hh
Go to the documentation of this file.
1 #pragma once
2 
5 #include <vcsn/ctx/traits.hh>
6 #include <vcsn/dyn/automaton.hh>
7 #include <vcsn/dyn/context.hh>
8 
9 namespace vcsn
10 {
11  /*--------------------.
12  | random_automaton. |
13  `--------------------*/
14 
43  template <typename Ctx>
44  mutable_automaton<Ctx>
45  random_automaton(const Ctx& ctx,
46  unsigned num_states, float density = 0.1,
47  unsigned num_initial = 1, unsigned num_final = 1,
48  boost::optional<unsigned> max_labels = {},
49  float loop_chance = 0.0, const std::string& weights = "")
50  {
51  require(0 <= density && density <= 1,
52  "random_automaton: density must be in [0,1]");
53  require(0 <= loop_chance && loop_chance <= 1,
54  "random_automaton: loop chance must be in [0,1]");
55 
56  using detail::irange;
57  using automaton_t = mutable_automaton<Ctx>;
58  using state_t = state_t_of<automaton_t>;
59  auto res = make_shared_ptr<automaton_t>(ctx);
60 
61  auto& gen = make_random_engine();
62 
63  const auto& ws = *ctx.weightset();
64 
65  // Labels.
66  const auto& ls = *ctx.labelset();
67  if (max_labels)
68  {
69  require(0 < *max_labels,
70  "random_automaton: max number of labels cannot be null");
71 
72  require(*max_labels <= boost::distance(ls.generators()) + ls.has_one(),
73  "random_automaton: max number of labels cannot be greater "
74  "than the number of generators");
75  }
76  else
77  {
78  max_labels = (boost::distance(ls.generators()) + ls.has_one());
79  require(0 < *max_labels,
80  "random_automaton: empty labelset: ", ls);
81  }
82  auto num_labels = std::uniform_int_distribution<>(1, *max_labels);
83 
84  auto random_weight = [&weights, &ws]()
85  {
86  return weights.empty() ? ws.one() : vcsn::random_weight(ws, weights);
87  };
88 
89  auto states = std::vector<state_t>{};
90  states.reserve(num_states);
91  // Indirect access to states[] to help random selection of successors.
92  auto state_randomizer = std::vector<int>{};
93  state_randomizer.reserve(num_states);
94 
95  // Using Sgi::hash_set instead of std::set for these sets is 3
96  // times slower (tested on a 50000 states example). These are
97  // indexes in states[].
98  using state_set = std::set<int>;
99  state_set worklist;
100  // Reachability from state[0] (_not_ from pre()).
101  state_set unreachables;
102 
103  for (unsigned i: detail::irange(num_states))
104  {
105  states.emplace_back(res->new_state());
106  state_randomizer.emplace_back(i);
107  // State 0 is "reachable" from 0.
108  if (i)
109  unreachables.emplace(i);
110  if (i < num_initial)
111  res->set_initial(states[i]);
112  }
113  worklist.insert(0);
114 
115  // Select the final states.
116  for (unsigned i: detail::irange(num_final))
117  {
118  auto dis = std::uniform_int_distribution<>(i, num_states - 1);
119  int index = dis(gen);
120  res->set_final(states[state_randomizer[index]]);
121  // Swap it at the beginning of state_randomizer, so we cannot
122  // pick it again.
123  std::swap(state_randomizer[index], state_randomizer[i]);
124  }
125 
126  // We want to connect each state to a number of successors between
127  // 1 and n. If the probability to connect to each successor is d,
128  // the number of connected successors follows a binomial distribution.
129  auto bin = std::binomial_distribution<>(num_states - 1, density);
130 
131  // Pick a member of a container following a uniform distribution.
132  auto pick = make_random_selector(gen);
133 
134  while (!worklist.empty())
135  {
136  auto src = states[*worklist.begin()];
137  worklist.erase(worklist.begin());
138 
139  // Choose a random number of successors (at least one), using
140  // a binomial distribution.
141  unsigned nsucc = 1 + bin(gen);
142 
143  // Connect to NSUCC randomly chosen successors. We want at
144  // least one unreachable successors among these if there are
145  // some.
146  bool saw_unreachable = false;
147  auto possibilities = num_states;
148  while (nsucc--)
149  {
150  // The index (in states[]) of the destination.
151  unsigned dst = -1;
152  // No connection to unreachable successors so far. This is
153  // our last chance, so force it now.
154  if (nsucc == 0
155  && !saw_unreachable
156  && !unreachables.empty())
157  {
158  // Pick a random unreachable state.
159  dst = pick.pop(unreachables);
160  worklist.insert(dst);
161  }
162  else
163  {
164  // Pick the index of a random state.
165  auto dis
166  = std::uniform_int_distribution<>(0, possibilities - 1);
167  int index = dis(gen);
168  possibilities--;
169 
170  dst = state_randomizer[index];
171 
172  // Permute it with state_randomizer[possibilities], so
173  // we cannot pick it again.
174  std::swap(state_randomizer[index],
175  state_randomizer[possibilities]);
176 
177  state_set::iterator j = unreachables.find(dst);
178  if (j != unreachables.end())
179  {
180  worklist.insert(dst);
181  unreachables.erase(j);
182  saw_unreachable = true;
183  }
184  }
185  auto n = num_labels(gen);
186  for (auto _: detail::irange(n))
187  res->add_transition(src, states[dst],
188  random_label(ls, gen),
189  random_weight());
190  }
191  }
192 
193  // Add loops.
194  if (0 < loop_chance)
195  {
196  auto dis = std::bernoulli_distribution(loop_chance);
197  for (auto s : res->states())
198  if (dis(gen))
199  res->add_transition(s, s,
200  random_label(*ctx.labelset(), gen),
201  random_weight());
202 
203  }
204 
205  return res;
206  }
207 
208  namespace dyn
209  {
210  namespace detail
211  {
213  template <typename Ctx, typename NumStates, typename Density,
214  typename NumInitial, typename NumFinal,
215  typename MaxLabels, typename LoopChance, typename String>
216  automaton
218  unsigned num_states, float density,
219  unsigned num_initial, unsigned num_final,
220  boost::optional<unsigned> max_labels,
221  float loop_chance,
222  const std::string& weights)
223  {
224  const auto& c = ctx->as<Ctx>();
225  return vcsn::random_automaton(c, num_states, density,
226  num_initial, num_final,
227  max_labels,
228  loop_chance,
229  weights);
230  }
231  }
232  }
233 
234 
235  /*----------------------------------.
236  | random_automaton_deterministic. |
237  `----------------------------------*/
238 
239  template <typename Ctx>
240  mutable_automaton<Ctx>
241  random_automaton_deterministic(const Ctx& ctx, unsigned num_states)
242  {
243  require(0 < num_states, "num_states must be > 0");
244 
245  using automaton_t = mutable_automaton<Ctx>;
246  using state_t = state_t_of<automaton_t>;
247  automaton_t res = make_shared_ptr<automaton_t>(ctx);
248 
249  auto& gen = make_random_engine();
250  auto dis = std::uniform_int_distribution<int>(0, num_states - 1);
251 
252  auto states = std::vector<state_t>{};
253  states.reserve(num_states);
254 
255  for (auto _: detail::irange(num_states))
256  states.emplace_back(res->new_state());
257 
258  for (auto i: detail::irange(num_states))
259  for (auto l : ctx.labelset()->generators())
260  res->add_transition(states[i], states[dis(gen)], l,
261  ctx.weightset()->one());
262 
263  res->set_initial(states[dis(gen)]);
264  res->set_final(states[dis(gen)]);
265 
266  return res;
267  }
268 
269  namespace dyn
270  {
271  namespace detail
272  {
274  template <typename Ctx, typename>
275  automaton
276  random_automaton_deterministic(const context& ctx, unsigned num_states)
277  {
278  const auto& c = ctx->as<Ctx>();
279  return vcsn::random_automaton_deterministic(c, num_states);
280  }
281  }
282  }
283 }
automaton random_automaton_deterministic(const context &ctx, unsigned num_states)
Bridge.
return res
Definition: multiply.hh:398
std::shared_ptr< detail::mutable_automaton_impl< Context >> mutable_automaton
Definition: fwd.hh:25
mutable_automaton< Ctx > random_automaton(const Ctx &ctx, unsigned num_states, float density=0.1, unsigned num_initial=1, unsigned num_final=1, boost::optional< unsigned > max_labels={}, float loop_chance=0.0, const std::string &weights="")
Produce a random automaton.
void require(Bool b, Args &&...args)
If b is not verified, raise an error with args as message.
Definition: raise.hh:91
WeightSet::value_t random_weight(const WeightSet &ws, const std::string &param={})
Generate a random weight.
std::mt19937 & make_random_engine()
Generate a unique random device.
Definition: random.cc:6
auto & as()
Downcast to the exact type.
Definition: context.hh:36
auto irange(Integer last)
Generate an integer range.
Definition: irange.hh:11
Definition: a-star.hh:8
random_selector< RandomGenerator > make_random_selector(RandomGenerator &g)
Definition: random.hh:102
Template-less root for contexts.
Definition: context.hh:16
mutable_automaton< Ctx > random_automaton_deterministic(const Ctx &ctx, unsigned num_states)
typename detail::state_t_of_impl< base_t< ValueSet >>::type state_t_of
Definition: traits.hh:64
expressionset< Context >::value_t random_label(const expressionset< Context > &rs, RandomGenerator &gen=RandomGenerator())
Random label from expressionset: limited to a single label.
automaton random_automaton(const context &ctx, unsigned num_states, float density, unsigned num_initial, unsigned num_final, boost::optional< unsigned > max_labels, float loop_chance, const std::string &weights)
Bridge.