PaGMO  1.1.5
racing.cpp
1 #include "racing.h"
2 #include "../rng.h"
3 #include "../problem/base_stochastic.h"
4 #include <boost/math/distributions/chi_squared.hpp>
5 #include <boost/math/distributions/students_t.hpp>
6 #include <boost/math/distributions/normal.hpp>
7 
8 #include <utility>
9 
10 namespace pagmo{ namespace util{
11 
12 namespace racing{
14 
16 
22 racing_population::racing_population(const population &pop): population(pop)
23 {
24 }
25 
27 
33 racing_population::racing_population(const problem::base &prob): population(prob)
34 {
35 }
36 
38 
46 void racing_population::set_x_noeval(const size_type idx, const decision_vector &x)
47 {
48  if (idx >= size()) {
49  pagmo_throw(index_error,"invalid individual position");
50  }
51  if (!problem().verify_x(x)) {
52  pagmo_throw(value_error,"decision vector is not compatible with problem");
53 
54  }
55  // Set decision vector.
56  m_container[idx].cur_x = x;
57 }
58 
60 
69 void racing_population::set_fc(const size_type idx, const fitness_vector &f, const constraint_vector &c)
70 {
71  if (idx >= size()) {
72  pagmo_throw(index_error, "Invalid individual position in set_fc");
73  }
74  if (f.size() != problem().get_f_dimension()) {
75  pagmo_throw(value_error, "Incompatible fitness dimension in set_fc");
76  }
77  if (c.size() != problem().get_c_dimension()) {
78  pagmo_throw(value_error, "Incompatible constraint dimension in set_fc");
79  }
80  m_container[idx].cur_f = f;
81  m_container[idx].cur_c = c;
82  // NOTE: As update_dom() uses best_f and best_c when computing Pareto ranks
83  // and hence, racing_population can be used a way to by pass this in order
84  // to respect more the concept of racing
85  m_container[idx].best_f = f;
86  m_container[idx].best_c = c;
87  update_dom(idx);
88 }
89 
90 
92 
102 void racing_population::push_back_noeval(const decision_vector &x)
103 {
104  // No checking on the validity of x:
105  // Accept a dummy x, as when using racing_population, the main focus is on
106  // the fitness and constraint vectors. The main purpose of this function is
107  // to allocate the spaces but skip the evaluation.
108 
109  // Store sizes temporarily.
110  const fitness_vector::size_type f_size = problem().get_f_dimension();
111  const constraint_vector::size_type c_size = problem().get_c_dimension();
112  const decision_vector::size_type p_size = problem().get_dimension();
113  // Push back an empty individual.
114  m_container.push_back(individual_type());
115  m_dom_list.push_back(std::vector<size_type>());
116  m_dom_count.push_back(0);
117  // Resize individual's elements.
118  m_container.back().cur_x.resize(p_size);
119  m_container.back().cur_v.resize(p_size);
120  m_container.back().cur_c.resize(c_size);
121  m_container.back().cur_f.resize(f_size);
122 
123  // As we bypass set_x which will allocate spaces for bests, they must be
124  // explicitly allocated here.
125  m_container.back().best_f.resize(f_size);
126  m_container.back().best_c.resize(c_size);
127 
128  // Set the individual.
129  set_x_noeval(m_container.size() - 1, x);
130 }
131 
132 
134 
139 std::vector<double> racing_population::get_rankings() const
140 {
141  std::vector<double> rankings(size());
142  std::vector<size_type> raw_order = get_best_idx(size());
143 
144  int cur_rank = 1;
145  for(size_type i = 0; i < raw_order.size(); i++){
146  int ind_idx = raw_order[i];
147  rankings[ind_idx] = cur_rank;
148  cur_rank++;
149  }
150 
151  // --Adjust ranking to cater for ties--
152  // 1. Check consecutively ranked individuals whether they are tied.
153  std::vector<bool> tied(size() - 1, false);
154  if(problem().get_f_dimension() == 1){
155  // Single-objective case
156  population::trivial_comparison_operator comparator(*this);
157  for(size_type i = 0; i < size() - 1; i++){
158  if (!comparator(i, i+1) && !comparator(i+1, i)){
159  tied[i] = true;
160  }
161  }
162  }
163  else{
164  // Multi-objective case
165  population::crowded_comparison_operator comparator(*this);
166  for(size_type i = 0; i < size() - 1; i++){
167  if (!comparator(i, i+1) && !comparator(i+1, i)){
168  tied[i] = true;
169  }
170  }
171  }
172 
173  // 2. For all the individuals who are tied, modify their rankings to
174  // be the average rankings in case of no tie.
175  size_type cur_pos = 0;
176  size_type begin_avg_pos;
177  while(cur_pos < tied.size()){
178  begin_avg_pos = cur_pos;
179  while(tied[cur_pos]==1){
180  cur_pos++;
181  if(cur_pos >= tied.size()){
182  break;
183  }
184  }
185 
186  double avg_rank = 0;
187  for(size_type i = begin_avg_pos; i <= cur_pos; i++){
188  avg_rank += rankings[i] / ((double)cur_pos - begin_avg_pos + 1);
189  }
190  for(size_type i = begin_avg_pos; i <= cur_pos; i++){
191  rankings[i] = avg_rank;
192  }
193 
194  // If no tie at all for this begin pos
195  if(begin_avg_pos == cur_pos){
196  cur_pos++;
197  }
198  }
199  return rankings;
200 }
201 
202 
204 
214 void f_race_assign_ranks(std::vector<racer_type>& racers, const racing_population& racing_pop_full)
215 {
216  // Update ranking to be used for stat test
217  // Note that the ranking returned by get_best_idx() requires some post-processing,
218  // as individuals not currently in race should be ignored.
219 
220  typedef population::size_type size_type;
221 
222  // Construct a more condensed population with only active individuals
223  racing_population racing_pop(racing_pop_full.problem());
224  decision_vector dummy_x(racing_pop_full.problem().get_dimension(), 0);
225  std::vector<size_type> idx_mapping;
226  for(size_type i = 0; i < racers.size(); i++){
227  if(racers[i].active){
228  racing_pop.push_back_noeval(dummy_x);
229  racing_pop.set_fc(racing_pop.size()-1, racing_pop_full.get_individual(i).cur_f, racing_pop_full.get_individual(i).cur_c);
230  idx_mapping.push_back(i);
231  }
232  }
233 
234  // Get the rankings in the sense of satistical testing
235  std::vector<double> rankings = racing_pop.get_rankings();
236  for(unsigned int i = 0; i < racing_pop.size(); i++){
237  racers[idx_mapping[i]].m_hist.push_back(rankings[i]);
238  }
239 
240  // Update mean of rank (which also reflects the sum of rank, useful for
241  // later pair-wise test)
242  for(size_type i = 0; i < rankings.size(); i++){
243  racer_type& cur_racer = racers[idx_mapping[i]];
244  cur_racer.m_mean = 0;
245  for(unsigned int j = 0; j < cur_racer.length(); j++){
246  cur_racer.m_mean += (cur_racer.m_hist[j]) / (double)cur_racer.length();
247  }
248  }
249 
250 }
251 
253 
262 void f_race_adjust_ranks(std::vector<racer_type>& racers, const std::vector<population::size_type>& deleted_racers)
263 {
264  for(unsigned int i = 0; i < racers.size(); i++){
265  if(!racers[i].active) continue;
266  for(unsigned int j = 0; j < racers[i].length(); j++){
267  int adjustment = 0;
268  for(unsigned int k = 0; k < deleted_racers.size(); k++){
269  if(racers[i].m_hist[j] > racers[deleted_racers[k]].m_hist[j]){
270  adjustment++;
271  }
272  }
273  racers[i].m_hist[j] -= adjustment;
274  }
275  }
276 }
277 
279 
293 stat_test_result core_friedman_test(const std::vector<std::vector<double> >& X, double delta)
294 {
295  pagmo_assert(X.size() > 0);
296 
297  unsigned int N = X.size(); // # of different configurations
298  unsigned int B = X[0].size(); // # of different instances
299 
300  // Compute mean rank
301  std::vector<double> X_mean(N, 0);
302  for(unsigned int i = 0; i < N; i++){
303  for(unsigned int j = 0; j < X[i].size(); j++){
304  X_mean[i] += X[i][j];
305  }
306  X_mean[i] /= (double)X[i].size();
307  }
308 
309  // Fill in R and T
310  std::vector<double> R(N, 0);
311  double A1 = 0;
312  double C1 = B * N * (N+1) * (N+1) / 4.0;
313 
314  for(unsigned int i = 0; i < N; i++){
315  for(unsigned int j = 0; j < B; j++){
316  R[i] += X[i][j];
317  A1 += (X[i][j])*(X[i][j]);
318  }
319  }
320 
321  double T1 = 0;
322  for(unsigned int i = 0; i < N; i++){
323  T1 += ((R[i] - B*(N+1)/2.0) * (R[i] - B*(N+1)/2.0));
324  }
325  T1 *= (N - 1) / (A1 - C1);
326 
327  using boost::math::chi_squared;
328  using boost::math::students_t;
329  using boost::math::quantile;
330 
331  chi_squared chi_squared_dist(N - 1);
332 
333  double delta_quantile = quantile(chi_squared_dist, 1 - delta);
334 
335  // Null hypothesis 0: All the ranks observed are equally likely
336  bool null_hypothesis_0 = (boost::math::isnan(T1) || T1 < delta_quantile);
337 
338  stat_test_result res;
339 
340  // True if null hypothesis is rejected -- some differences between
341  // the observations will be significant
342  res.trivial = null_hypothesis_0;
343 
344  res.is_better = std::vector<std::vector<bool> >(N, std::vector<bool>(N, false));
345 
346  if(!null_hypothesis_0){
347  std::vector<std::vector<bool> >& is_better = res.is_better;
348 
349  students_t students_t_dist((N-1)*(B-1));
350  double t_delta2_quantile = quantile(students_t_dist, 1 - delta/2.0);
351  double Q = sqrt(((A1-C1) * 2.0 * B / ((B-1) * (N-1))) * (1 - T1 / (B*(N-1))));
352  for(unsigned int i = 0; i < N; i++){
353  for(unsigned int j = i + 1; j < N; j++){
354  double diff_r = fabs(R[i] - R[j]);
355  // Check if a pair is statistically significantly different
356  if(diff_r > t_delta2_quantile * Q){
357  if(X_mean[i] < X_mean[j]){
358  is_better[i][j] = true;
359  }
360  if(X_mean[j] < X_mean[i]){
361  is_better[j][i] = true;
362  }
363  }
364  }
365  }
366  }
367 
368  return res;
369 
370 }
371 
373 
379 stat_test_result friedman_test(std::vector<racer_type> &racers, const std::vector<population::size_type> &in_race, const racing_population &pop, double delta)
380 {
381  f_race_assign_ranks(racers, pop);
382 
383  // Observation data (TODO: is this necessary ? ... a lot of memory
384  // allocation gets done here and we already have in memory all we need.
385  // could we not pass by reference directly racers and in_race to the
386  // friedman test?)
387  std::vector<std::vector<double> > X;
388  for(unsigned int i = 0; i < in_race.size(); i++){
389  X.push_back(racers[in_race[i]].m_hist);
390  }
391 
392  // Friedman Test
393  stat_test_result ss_result = core_friedman_test(X, delta);
394  return ss_result;
395 }
396 
398 
405 stat_test_result wilcoxon_ranksum_test(std::vector<racer_type> &racers, const std::vector<population::size_type> &in_race, const racing_population& wilcoxon_pop, double delta)
406 {
407  pagmo_assert(in_race.size() == 2);
408 
409  // Get the rankings of all the data points
410  std::vector<double> rankings = wilcoxon_pop.get_rankings();
411 
412  // Need to update racers in case no statistical significant results can be
413  // found, which will then default to selecting the one with best mean. Two
414  // specific individuals in the wilcoxon_pop correspond to the newest two
415  // evaluated points.
416  racers[in_race[0]].m_hist.push_back(rankings[wilcoxon_pop.size()/2 - 1]);
417  racers[in_race[1]].m_hist.push_back(rankings[wilcoxon_pop.size() - 1]);
418 
419  // Compute the mean ranks
420  for(std::vector<population::size_type>::const_iterator it = in_race.begin(); it != in_race.end(); it++){
421  racer_type& cur_racer = racers[*it];
422  cur_racer.m_mean = 0;
423  for(unsigned int j = 0; j < cur_racer.length(); j++){
424  cur_racer.m_mean += (cur_racer.m_hist[j]) / (double)cur_racer.length();
425  }
426  }
427 
428  std::vector<std::vector<double> > X(2);
429  unsigned int n_samples = wilcoxon_pop.size() / 2;
430  for(unsigned int i = 0; i < 2; i++){
431  X[i].resize(n_samples);
432  }
433  for(unsigned int i = 0; i < wilcoxon_pop.size(); i++){
434  X[i%2][i/2] = rankings[i];
435  }
436  return core_wilcoxon_ranksum_test(X, delta);
437 }
438 
439 // Helper routine for Wilcoxon test
440 std::size_t wilcoxon_faculty( std::size_t n )
441 {
442  if( n == 1 )
443  return( 1 );
444 
445  return( n * wilcoxon_faculty( n-1 ) );
446 }
447 
448 // Helper routine for Wilcoxon test
449 double wilcoxon_frequency( double u, int sampleSizeA, int sampleSizeB )
450 {
451  if( u < 0. || sampleSizeA < 0 || sampleSizeB < 0 )
452  return( 0. );
453 
454  if( u == 0 && sampleSizeA >= 0 && sampleSizeB >= 0 )
455  return( 1 );
456 
457  return( wilcoxon_frequency( u - sampleSizeB, sampleSizeA - 1, sampleSizeB ) + wilcoxon_frequency( u, sampleSizeA, sampleSizeB - 1 ) );
458 }
459 
460 // Performs Wilcoxon Rank Sum Test (a.k.a.Wilcoxon–Mann–Whitney test)
461 // Intended to be used when the number of active racers is only two, as it has
462 // been reported that under such circumstances this test is more data efficient
463 // than Friedman test.
464 stat_test_result core_wilcoxon_ranksum_test(const std::vector<std::vector<double> > &X, double delta)
465 {
466  if(X.size() != 2){
467  pagmo_throw(value_error, "Wilcoxon rank-sum test is applicable only when the group size is 2");
468  }
469  unsigned int N = 2;
470 
471  // Compute sum of ranks
472  std::vector<double> rank_sum(N, 0);
473  for(unsigned int i = 0; i < N; i++){
474  for(unsigned int j = 0; j < X[i].size(); j++){
475  rank_sum[i] += X[i][j];
476  }
477  }
478 
479  // Fill in pair-wise comparison results
480  stat_test_result res(N);
481 
482  int sizeA = X[0].size();
483  int sizeB = X[1].size();
484  if(sizeA < 12 && sizeB < 12){
485  // Cannot use normal approximation for the rank-sum statistic when
486  // sample size is small. Boost does not have the required Wilcoxon
487  // rank-sum distribution (currently in their todo list)... This piece
488  // of code is adapted from the Shark machine learning library.
489  int wA = rank_sum[0];
490  //int wB = rank_sum[1];
491  double uA = wA - sizeA * ( sizeA + 1 ) / 2.;
492  //double uB = wB - sizeB * ( sizeB + 1 ) / 2.;
493  double pA = (double)( wilcoxon_faculty( sizeA ) * wilcoxon_faculty( sizeB ) ) / (double)( wilcoxon_faculty( sizeA + sizeB ) ) * wilcoxon_frequency( uA, sizeA, sizeB );
494  //double pB = (double)( wilcoxon_faculty( sizeA ) * wilcoxon_faculty( sizeB ) ) / (double)( wilcoxon_faculty( sizeA + sizeB ) ) * wilcoxon_frequency( uB, sizeA, sizeB );
495  if(pA < delta){
496  res.trivial = false;
497  if(rank_sum[0] > rank_sum[1]){
498  res.is_better[1][0] = true;
499  }
500  else{
501  res.is_better[0][1] = true;
502  }
503  }
504  }
505  else{
506  // Use the normal approximation
507  using boost::math::normal;
508  int n1 = X[0].size();
509  int n2 = X[1].size();
510  normal normal_dist(n1*n2/2, sqrt(n1*n2*(n1+n2+1.0)/12.0));
511  double delta_quantile_upp = quantile(normal_dist, 1 - delta);
512  if(rank_sum[0] > rank_sum[1] && rank_sum[0] > delta_quantile_upp){
513  res.trivial = false;
514  res.is_better[1][0] = true;
515  }
516  else if(rank_sum[1] > rank_sum[0] && rank_sum[1] > delta_quantile_upp){
517  res.trivial = false;
518  res.is_better[0][1] = true;
519  }
520  }
521 
522  return res;
523 }
524 
526 
527 }}}
Root PaGMO namespace.
std::vector< double > decision_vector
Decision vector type.
Definition: types.h:40
std::vector< double > fitness_vector
Fitness vector type.
Definition: types.h:42
std::vector< double > constraint_vector
Constraint vector type.
Definition: types.h:44
container_type::size_type size_type
Population size type.
Definition: population.h:192