PaGMO  1.1.5
moea_d.cpp
1 /*****************************************************************************
2  * Copyright (C) 2004-2015 The PaGMO development team, *
3  * Advanced Concepts Team (ACT), European Space Agency (ESA) *
4  * *
5  * https://github.com/esa/pagmo *
6  * *
7  * act@esa.int *
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License *
20  * along with this program; if not, write to the *
21  * Free Software Foundation, Inc., *
22  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
23  *****************************************************************************/
24 
25 
26 #include <string>
27 #include <vector>
28 #include <algorithm>
29 #include<limits>
30 
31 #include <boost/random/uniform_real.hpp>
32 #include <boost/random/uniform_int.hpp>
33 #include <boost/random/variate_generator.hpp>
34 #include <boost/math/special_functions/binomial.hpp>
35 
36 #include "../exceptions.h"
37 #include "../population.h"
38 #include "../problem/base.h"
39 #include "../island.h"
40 #include "../population.h"
41 #include "../problem/decompose.h"
42 #include "../util/discrepancy.h"
43 #include "../util/neighbourhood.h"
44 #include "../types.h"
45 #include "base.h"
46 #include "moea_d.h"
47 
48 namespace pagmo { namespace algorithm {
49 
50 
52 
66 moead::moead(int gen,
67  weight_generation_type weight_generation,
69  double realb,
70  unsigned int limit,
71  double cr,
72  double f,
73  double eta_m,
74  bool preserve_diversity
75  ) : base(),
76  m_gen(gen),
77  m_T(T),
78  m_weight_generation(weight_generation),
79  m_realb(realb),
80  m_limit(limit),
81  m_cr(cr),
82  m_f(f),
83  m_eta_m(eta_m),
84  m_preserve_diversity(preserve_diversity)
85 {
86  // Sanity checks
87  if (gen < 0) {
88  pagmo_throw(value_error,"number of generations must be nonnegative");
89  }
90 
91  if(m_weight_generation != RANDOM && m_weight_generation != GRID && m_weight_generation != LOW_DISCREPANCY) {
92  pagmo_throw(value_error,"non existing weight generation method");
93  }
94 
95  if(realb > 1.0 || realb < 0) {
96  pagmo_throw(value_error,"The neighbor type chance realb needs to be in [0,1]");
97  }
98 
99  if(cr > 1.0 || cr < 0) {
100  pagmo_throw(value_error,"Differential Evolution crossover cr needs to be in [0,1]");
101  }
102 
103  if(f > 1.0 || f < 0) {
104  pagmo_throw(value_error,"Differential Evolution f parameter needs to be in [0,1]");
105  }
106 
107  if(eta_m < 0) {
108  pagmo_throw(value_error,"Distribution index for the polynomial mutation needs to be > 0");
109  }
110 }
111 
114 {
115  return base_ptr(new moead(*this));
116 }
117 
118 //Recursive function building all m-ple of elements of X summing to s
119 void moead::reksum(std::vector<std::vector<double> > &retval,
120  const std::vector<unsigned int>& X,
121  unsigned int m,
122  unsigned int s,
123  std::vector<double> eggs) const {
124 
125  if (m==1) {
126  if (std::find(X.begin(),X.end(),s) == X.end()) { //not found
127  return;
128  } else {
129  eggs.push_back(s);
130  retval.push_back(eggs);
131  }
132  } else {
133  for (unsigned int i=0; i<X.size(); ++i) {
134  eggs.push_back(X[i]);
135  reksum(retval,X,m-1,s-X[i],eggs);
136  eggs.pop_back();
137  }
138  }
139 }
140 
142 
152  std::vector<fitness_vector> moead::generate_weights(const unsigned int n_f, const unsigned int n_w) const {
153 
154  // Sanity check
155  if (n_f > n_w) {
156  pagmo_throw(value_error,"To allow weight be generated correctly the number of weights must be strictly larger than the number of objectives");
157  }
158 
159  // Definition of useful probability distributions
160  boost::uniform_real<double> uniform(0.0,1.0);
161  boost::variate_generator<boost::lagged_fibonacci607 &, boost::uniform_real<double> > r_dist(m_drng,uniform);
162 
163  std::vector<fitness_vector> retval;
164  if(m_weight_generation == GRID) {
165  //find the largest H resulting in a population smaller or equal to NP
166  unsigned int H;
167  if (n_f == 2) {
168  H = n_w-1;
169  } else if (n_f == 3) {
170  H = floor(0.5 * (sqrt(8*n_w + 1) - 3));
171  } else {
172  H = 1;
173  while(boost::math::binomial_coefficient<double>(H+n_f-1, n_f-1) <= n_w) {
174  ++H;
175  }
176  H--;
177  }
178 
179  // We check that NP equals the population size resulting from H
180  if (fabs(n_w-(boost::math::binomial_coefficient<double>(H+n_f-1, n_f-1))) > 1E-8) {
181  std::ostringstream error_message;
182  error_message << "Invalid population size. Select " << boost::math::binomial_coefficient<double>(H+n_f-1, n_f-1)
183  << " or " << boost::math::binomial_coefficient<double>(H+1+n_f-1, n_f-1)
184  << ".";
185  pagmo_throw(value_error,error_message.str());
186  }
187 
188  // We generate the weights
189  std::vector<unsigned int> range;
190  for (unsigned int i=0; i<H+1;++i) {
191  range.push_back(i);
192  }
193  reksum(retval, range, n_f, H);
194  for(unsigned int i=0; i< retval.size(); ++i) {
195  for(unsigned int j=0; j< retval[i].size(); ++j) {
196  retval[i][j] /= H;
197  }
198  }
199 
200  } else if(m_weight_generation == LOW_DISCREPANCY) {
201  for(unsigned int i = 0; i< n_f; ++i) {
202  retval.push_back(fitness_vector(n_f,0.0));
203  retval[i][i] = 1.0;
204  }
205  pagmo::util::discrepancy::simplex generator(n_f,1);
206  for(unsigned int i = n_f; i <n_w; ++i) {
207  retval.push_back(generator());
208  }
209 
210  } else if(m_weight_generation == RANDOM) {
211  pagmo::util::discrepancy::project_2_simplex projection(n_f);
212  for (unsigned int i = 0; i<n_w; ++i) {
213  fitness_vector dummy(n_f-1,0.0);
214  for(unsigned int j = 0; j <n_f-1; ++j) {
215  dummy[j] = r_dist();
216  }
217  retval.push_back(projection(dummy));
218  }
219  }
220  return retval;
221  }
222 
223 // Performs polynomial mutation (code from nsgaII)
224 void moead::mutation(decision_vector& child, const pagmo::population& pop, double rate) const
225 {
226 
227  problem::base::size_type D = pop.problem().get_dimension();
228  const decision_vector &lb = pop.problem().get_lb(), &ub = pop.problem().get_ub();
229  double rnd, delta1, delta2, mut_pow, deltaq;
230  double y, yl, yu, val, xy;
231 
232  //This implements the real polinomial mutation of an individual
233  for (pagmo::problem::base::size_type j=0; j < D; ++j){
234  if (m_drng() <= rate) {
235  y = child[j];
236  yl = lb[j];
237  yu = ub[j];
238  delta1 = (y-yl)/(yu-yl);
239  delta2 = (yu-y)/(yu-yl);
240  rnd = m_drng();
241  mut_pow = 1.0/(m_eta_m+1.0);
242  if (rnd <= 0.5)
243  {
244  xy = 1.0-delta1;
245  val = 2.0*rnd+(1.0-2.0*rnd)*(pow(xy,(m_eta_m+1.0)));
246  deltaq = pow(val,mut_pow) - 1.0;
247  }
248  else
249  {
250  xy = 1.0-delta2;
251  val = 2.0*(1.0-rnd)+2.0*(rnd-0.5)*(pow(xy,(m_eta_m+1.0)));
252  deltaq = 1.0 - (pow(val,mut_pow));
253  }
254  y = y + deltaq*(yu-yl);
255  if (y<yl) y = yl;
256  if (y>yu) y = yu;
257  child[j] = y;
258  }
259  }
260 }
261 
262 void moead::mating_selection(std::vector<population::size_type> &list, int n, int type,const std::vector<std::vector<population::size_type> >& neigh_idx) const {
263  list.clear();
264  std::vector<population::size_type>::size_type ss = neigh_idx[n].size(), p;
265 
266  boost::uniform_int<int> idx(0,neigh_idx.size()-1);
267  boost::variate_generator<boost::mt19937 &, boost::uniform_int<int> > p_idx(m_urng,idx);
268 
269  while(list.size()<2)
270  {
271  if(type==1){
272  p = neigh_idx[n][p_idx()%ss];
273  } else {
274  p = p_idx();
275  }
276 
277  bool flag = true;
278  for(std::vector<population::size_type>::size_type i=0; i<list.size(); i++)
279  {
280  if(list[i]==p) // p is in the list
281  {
282  flag = false;
283  break;
284  }
285  }
286  if(flag) list.push_back(p);
287  }
288 }
289 
291 
292 void moead::evolve(population &pop) const
293 {
294  // Let's store some useful variables.
295  const problem::base &prob = pop.problem();
296  const population::size_type NP = pop.size();
297  const decision_vector &lb = prob.get_lb(), &ub = prob.get_ub();
298 
299  // And make some sanity checks
300  if ( prob.get_f_dimension() < 2 ) {
301  pagmo_throw(value_error, "The problem is not multiobjective, try some other algorithm than MOEA/D");
302  }
303  if ( m_T > NP-1 ) {
304  pagmo_throw(value_error, "Too many neighbours specified");
305  }
306 
307  // Get out if there is nothing to do.
308  if (m_gen == 0) {
309  return;
310  }
311 
312  // Variate generators
313  boost::uniform_int<int> pop_idx(0,NP-1);
314  boost::variate_generator<boost::mt19937 &, boost::uniform_int<int> > p_idx(m_urng,pop_idx);
315 
316  // Initialize the candidate chromosome
317  decision_vector candidate(prob.get_dimension());
318 
319  // Compute the starting ideal point
320  fitness_vector ideal_point = pop.compute_ideal();
321 
322  // Generate the weights for NP decomposed problems
323  std::vector<fitness_vector> weights = generate_weights(prob.get_f_dimension(), NP);
324 
325  // We compute, for each weight vector, the m_T neighbouring ones
326  std::vector<std::vector<population::size_type> > neigh_idx;
328  for (unsigned int i=0; i < neigh_idx.size();++i) {
329  neigh_idx[i].erase(neigh_idx[i].begin());
330  neigh_idx[i].erase(neigh_idx[i].begin()+m_T, neigh_idx[i].end());
331  }
332 
333  // We create a decomposed problem which we will use not as a polymorphic problem,
334  // only as fitness and decomposed fitness evaluator
335  // (the construction parameter weights[0] is thus irrelevant). Note that the flag
336  // adtapt_ideal is set to true so that the problem will udate the ideal point at each call of the original obj fun.
337  pagmo::problem::decompose prob_decomposed(prob, problem::decompose::TCHEBYCHEFF, weights[0], ideal_point, true);
338 
339  // We create a pseudo-random permutation of the indexes 1..NP
340  std::vector<population::size_type> shuffle(NP);
341  for(pagmo::population::size_type i=0; i < shuffle.size(); ++i) shuffle[i] = i;
342 
343  fitness_vector new_f(prob.get_f_dimension()), f1(1), f2(1);
344 
345  // Main MOEA/D loop
346  for (int g = 0; g<m_gen; ++g) {
347  //Shuffle the indexes
348  std::random_shuffle(shuffle.begin(), shuffle.end(), p_idx);
349  for (population::size_type i = 0; i<NP;++i) {
350  // We consider the subproblem with index n
351  int n = shuffle[i];
352  // We select at random between a neighborhood and the whole pop
353  int type;
354  if(m_drng()<m_realb || !m_preserve_diversity) type = 1; // neighborhood
355  else type = 2; // whole population
356  // 1 - We select two mating partners (not n) in the neighbourhood
357  std::vector<population::size_type> p(2);
358  mating_selection(p,n,type,neigh_idx);
359 
360  // 2 - We produce and evaluate an offspring using a DE operator
361  for(decision_vector::size_type kk=0;kk<prob.get_dimension(); ++kk)
362  {
363  if (m_drng()<m_cr) {
364  /*Selected Two Parents*/
365  candidate[kk] = pop.get_individual(n).cur_x[kk] + m_f*(pop.get_individual(p[0]).cur_x[kk] - pop.get_individual(p[1]).cur_x[kk]);
366 
367  // Fix the bounds
368  if(candidate[kk]<lb[kk]){
369  candidate[kk] = lb[kk] + m_drng()*(pop.get_individual(n).cur_x[kk] - lb[kk]);
370  }
371  if(candidate[kk]>ub[kk]){
372  candidate[kk] = ub[kk] - m_drng()*(ub[kk] - pop.get_individual(n).cur_x[kk]);
373  }
374  } else {
375  candidate[kk] = pop.get_individual(n).cur_x[kk];
376  }
377  }
378  mutation(candidate, pop, 1.0 / prob.get_dimension());
379  // Note that we do not use prob, hence the cache of prob does not get these values.
380  // Note that the ideal point is here updated too
381  prob_decomposed.compute_original_fitness(new_f, candidate);
382 
383  // 3 - We update the ideal point (Not needed as its done in decomposed when the flag adapt_weight is true)
384  //for (fitness_vector::size_type j=0; j<prob.get_f_dimension(); ++j){
385  // if (new_f[j] < ideal_point[j]) ideal_point[j] = new_f[j];
386  //}
387  //prob_decomposed.set_ideal_point(ideal_point);
388 
389  // 4- We insert the newly found solution into the population
390  unsigned int size, time = 0;
391  // First try on problem n
392  prob_decomposed.compute_decomposed_fitness(f1,pop.get_individual(n).cur_f,weights[n]);
393  prob_decomposed.compute_decomposed_fitness(f2,new_f,weights[n]);
394  if(f2[0]<f1[0])
395  {
396  pop.set_x(n,candidate);
397  time++;
398  }
399  // Then on neighbouring problems up to m_limit (to preserve diversity)
400  if(type==1) size = neigh_idx[n].size(); // neighborhood
401  else size = NP; // whole population
402  std::vector<population::size_type> shuffle2(size);
403  for(pagmo::population::size_type k=0; k < shuffle2.size(); ++k) shuffle2[k] = k;
404  std::random_shuffle(shuffle2.begin(), shuffle2.end(), p_idx);
405  for (unsigned int k=0; k<shuffle2.size(); ++k) {
407  if(type==1) pick = neigh_idx[n][shuffle2[k]]; // neighborhood
408  else pick = shuffle2[k]; // whole population
409 
410  prob_decomposed.compute_decomposed_fitness(f1,pop.get_individual(pick).cur_f,weights[pick]);
411  prob_decomposed.compute_decomposed_fitness(f2,new_f,weights[pick]);
412  if(f2[0]<f1[0])
413  {
414  pop.set_x(pick,candidate);
415  time++;
416  }
417  // the maximal number of solutions updated is not allowed to exceed 'limit' if diversity is to be preserved
418  if(time>=m_limit && m_preserve_diversity) break;
419  }
420  }
421  }
422  // We reset the population memory (TODO... can be made much more efficient)
423  std::vector<decision_vector> X(NP,candidate);
424  for (population::size_type i=0; i < X.size(); ++i) X[i] = pop.get_individual(i).cur_x;
425  pop.clear();
426  for (population::size_type i=0; i < X.size(); ++i) pop.push_back(X[i]);
427 }
428 
430 std::string moead::get_name() const
431 {
432  return "MOEA/D-DE";
433 }
434 
436 
439 std::string moead::human_readable_extra() const
440 {
441  std::ostringstream s;
442  s << "gen:" << m_gen << ' ';
443  s << "weights:";
444  switch (m_weight_generation)
445  {
446  case RANDOM : s << "RANDOM" << ' ';
447  break;
448  case LOW_DISCREPANCY : s << "LOW_DISCREPANCY" << ' ';
449  break;
450  case GRID : s << "GRID" << ' ';
451  break;
452  }
453  s << "neighbours:" << m_T << ' ';
454  s << "realb:" << m_realb << ' ';
455  s << "limit:" << m_limit << ' ';
456  s << "cr:" << m_cr << ' ';
457  s << "f:" << m_f << ' ';
458  s << "preserve diversity:";
459  if (m_preserve_diversity) s << "True " ;
460  else s << "False ";
461  return s.str();
462 }
463 
464 }} //namespaces
465 
466 BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::algorithm::moead)
boost::shared_ptr< base > base_ptr
Alias for shared pointer to base algorithm.
Root PaGMO namespace.
void evolve(population &) const
Evolve implementation.
Definition: moea_d.cpp:292
std::vector< double > decision_vector
Decision vector type.
Definition: types.h:40
Halton sequence projected on a simplex.
Definition: discrepancy.h:176
std::string human_readable_extra() const
Extra human readable algorithm info.
Definition: moea_d.cpp:439
Weights are generated uniformly at random on the simplex.
Definition: moea_d.h:55
fitness_vector cur_f
Current fitness vector.
Definition: population.h:89
Decompose meta-problem.
Definition: decompose.h:62
const individual_type & get_individual(const size_type &) const
Get constant reference to individual at position n.
Definition: population.cpp:277
Base algorithm class.
fitness_vector compute_ideal() const
Compute and return the ideal objective vector.
Definition: population.cpp:499
Base problem class.
Definition: problem/base.h:148
Population class.
Definition: population.h:70
size_type get_dimension() const
Return global dimension.
Weights are generated on a uniform grid layed down on the simplex.
Definition: moea_d.h:56
void compute_original_fitness(fitness_vector &, const decision_vector &) const
Computes the original fitness.
Definition: decompose.cpp:151
Weights are generated on the simplex with low-discrepancy.
Definition: moea_d.h:57
weight_generation_type
Mechanism used to generate the weight vectors.
Definition: moea_d.h:54
void compute_decomposed_fitness(fitness_vector &, const fitness_vector &) const
Computes the decomposed fitness.
Definition: decompose.cpp:167
std::vector< double > fitness_vector
Fitness vector type.
Definition: types.h:42
const decision_vector & get_ub() const
Upper bounds getter.
container_type::size_type size_type
Population size type.
Definition: population.h:192
static void compute_neighbours(std::vector< std::vector< pagmo::population::size_type > > &, const std::vector< std::vector< double > > &)
decision_vector cur_x
Current decision vector.
Definition: population.h:83
rng_uint32 m_urng
Random number generator for unsigned integer values.
f_size_type get_f_dimension() const
Return fitness dimension.
const decision_vector & get_lb() const
Lower bounds getter.
The Tchebycheff method is used to perform the decomposition.
Definition: decompose.h:68
MOEA/D - DE.
Definition: moea_d.h:50
rng_double m_drng
Random number generator for double-precision floating point values.
base_ptr clone() const
Clone method.
Definition: moea_d.cpp:113
std::vector< fitness_vector > generate_weights(const unsigned int, const unsigned int) const
Weight generation.
Definition: moea_d.cpp:152
std::string get_name() const
Algorithm name.
Definition: moea_d.cpp:430
decision_vector::size_type size_type
Problem's size type: the same as pagmo::decision_vector's size type.
Definition: problem/base.h:160
moead(int gen=100, weight_generation_type=GRID, population::size_type=20, double realb=0.9, unsigned int limit=2, double CR=1.0, double F=0.5, double eta_m=20, bool preserve_diversity=true)
Constructor.
Definition: moea_d.cpp:66