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