PaGMO  1.1.5
pade.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 
30 #include <boost/math/special_functions/binomial.hpp>
31 #include <boost/random/uniform_real.hpp>
32 #include <boost/random/uniform_int.hpp>
33 #include <boost/random/variate_generator.hpp>
34 
35 #include "../exceptions.h"
36 #include "../population.h"
37 #include "../problem/base.h"
38 #include "../archipelago.h"
39 #include "../island.h"
40 #include "../population.h"
41 #include "../topology/fully_connected.h"
42 #include "../topology/custom.h"
43 #include "../topology/watts_strogatz.h"
44 #include "../problem/decompose.h"
45 #include "../util/discrepancy.h"
46 #include "../util/neighbourhood.h"
47 #include "../migration/worst_r_policy.h"
48 #include "../migration/best_s_policy.h"
49 #include "../types.h"
50 #include "base.h"
51 #include "pade.h"
52 
53 namespace pagmo { namespace algorithm {
55 
69 pade::pade(int gen, unsigned int threads, pagmo::problem::decompose::method_type method,
70  const pagmo::algorithm::base & solver, population::size_type T, weight_generation_type weight_generation,
71  const fitness_vector &z)
72  :base(),
73  m_gen(gen),
74  m_threads(threads),
75  m_method(method),
76  m_solver(solver.clone()),
77  m_T(T),
78  m_weight_generation(weight_generation),
79  m_z(z)
80 {
81  if (gen < 0) {
82  pagmo_throw(value_error,"number of generations must be nonnegative");
83  }
84 
85  //0 - Check whether method is implemented
86  if(m_weight_generation != RANDOM && m_weight_generation != GRID && m_weight_generation != LOW_DISCREPANCY) {
87  pagmo_throw(value_error,"non existing weight generation method");
88  }
89 }
90 
92 pade::pade(const pade &algo):
93  base(algo),
94  m_gen(algo.m_gen),
95  m_threads(algo.m_threads),
96  m_method(algo.m_method),
97  m_solver(algo.m_solver->clone()),
98  m_T(algo.m_T),
99  m_weight_generation(algo.m_weight_generation),
100  m_z(algo.m_z)
101 {}
102 
105 {
106  return base_ptr(new pade(*this));
107 }
108 
109 //Recursive function building all m-ple of elements of X summing to s
110 void pade::reksum(std::vector<std::vector<double> > &retval,
111  const std::vector<unsigned int>& X,
112  unsigned int m,
113  unsigned int s,
114  std::vector<double> eggs) const {
115 
116  if (m==1) {
117  if (std::find(X.begin(),X.end(),s) == X.end()) { //not found
118  return;
119  } else {
120  eggs.push_back(s);
121  retval.push_back(eggs);
122  }
123  } else {
124  for (unsigned int i=0; i<X.size(); ++i) {
125  eggs.push_back(X[i]);
126  reksum(retval,X,m-1,s-X[i],eggs);
127  eggs.pop_back();
128  }
129  }
130 }
131 
133 
140 std::vector<fitness_vector> pade::generate_weights(const unsigned int n_f, const unsigned int n_w) const {
141 
142  // Sanity check
143  if (n_f > n_w) {
144  pagmo_throw(value_error,"To allow weight be generated correctly the number of weights must be strictly larger than the number of objectives");
145  }
146 
147  // Definition of useful probability distributions
148  boost::uniform_real<double> uniform(0.0,1.0);
149  boost::variate_generator<boost::lagged_fibonacci607 &, boost::uniform_real<double> > r_dist(m_drng,uniform);
150 
151  std::vector<fitness_vector> retval;
152  if(m_weight_generation == GRID) {
153  //find the largest H resulting in a population smaller or equal to NP
154  unsigned int H;
155  if (n_f == 2) {
156  H = n_w-1;
157  } else if (n_f == 3) {
158  H = floor(0.5 * (sqrt(8*n_w + 1) - 3));
159  } else {
160  H = 1;
161  while(boost::math::binomial_coefficient<double>(H+n_f-1, n_f-1) <= n_w) {
162  ++H;
163  }
164  H--;
165  }
166 
167  // We check that NP equals the population size resulting from H
168  if (fabs(n_w-(boost::math::binomial_coefficient<double>(H+n_f-1, n_f-1))) > 1E-8) {
169  std::ostringstream error_message;
170  error_message << "Invalid population size. Select " << boost::math::binomial_coefficient<double>(H+n_f-1, n_f-1)
171  << " or " << boost::math::binomial_coefficient<double>(H+1+n_f-1, n_f-1)
172  << ".";
173  pagmo_throw(value_error,error_message.str());
174  }
175 
176  // We generate the weights
177  std::vector<unsigned int> range;
178  for (unsigned int i=0; i<H+1;++i) {
179  range.push_back(i);
180  }
181  reksum(retval, range, n_f, H);
182  for(unsigned int i=0; i< retval.size(); ++i) {
183  for(unsigned int j=0; j< retval[i].size(); ++j) {
184  retval[i][j] /= H;
185  }
186  }
187 
188  } else if(m_weight_generation == LOW_DISCREPANCY) {
189  for(unsigned int i = 0; i< n_f; ++i) {
190  retval.push_back(fitness_vector(n_f,0.0));
191  retval[i][i] = 1.0;
192  }
193  pagmo::util::discrepancy::simplex generator(n_f,1);
194  for(unsigned int i = n_f; i <n_w; ++i) {
195  retval.push_back(generator());
196  }
197 
198  } else if(m_weight_generation == RANDOM) {
199  pagmo::util::discrepancy::project_2_simplex projection(n_f);
200  for (unsigned int i = 0; i<n_w; ++i) {
201  fitness_vector dummy(n_f-1,0.0);
202  for(unsigned int j = 0; j <n_f-1; ++j) {
203  dummy[j] = r_dist();
204  }
205  retval.push_back(projection(dummy));
206  }
207  }
208  return retval;
209  }
210 
212 
217 void pade::evolve(population &pop) const
218 {
219  // Let's store some useful variables.
220  const problem::base &prob = pop.problem();
221  const population::size_type NP = pop.size();
222 
223  // And make some sanity checks
224  if ( prob.get_f_dimension() < 2 ) {
225  pagmo_throw(value_error, "The problem is not multiobjective, try some other algorithm than PaDE");
226  }
227 
228  if ( m_T > NP-1 ) {
229  pagmo_throw(value_error, "Too many neighbours specified");
230  }
231 
232  // Get out if there is nothing to do.
233  if (m_gen == 0) {
234  return;
235  }
236 
237  decision_vector dummy(pop.get_individual(0).cur_x.size(),0); //used for initialisation purposes
238  std::vector<decision_vector> X(NP,dummy); //set of population chromosomes
239 
240  // Copy the population chromosomes into X
241  for ( population::size_type i = 0; i<NP; i++ ) {
242  X[i] = pop.get_individual(i).cur_x;
243  }
244 
245  // Generate the weights for the NP decomposed problems
246  std::vector<fitness_vector> weights = generate_weights(prob.get_f_dimension(), NP);
247 
248  // We compute, for each weight vector, the neighbouring ones (this will form the topology later on)
249  std::vector<std::vector<population::size_type> > indices;
251 
252  // Create the archipelago of NP islands:
253  // each island in the archipelago solves a different single-objective problem.
254  // We use here the broadcast migration model. This will force, at each migration,
255  // to have individuals from all connected island to be inserted.
257 
258  // Sets random number generators of the archipelago using the algorithm urng to obtain
259  // a deterministic behaviour upon copy.
260  arch.set_seeds(m_urng());
261 
262  // Best individual will be selected for migration
263  const pagmo::migration::best_s_policy selection_policy;
264 
265  // As m_T neighbours are connected, we replace m_T individuals on the island
266  const pagmo::migration::worst_r_policy replacement_policy(m_T);
267 
268  //We create all the decomposed problems (one for each individual)
269  std::vector<pagmo::problem::base_ptr> problems_vector;
270  for(pagmo::population::size_type i=0; i<NP;++i) {
271  problems_vector.push_back(pagmo::problem::decompose(prob, m_method,weights[i],m_z).clone());
272  }
273 
274  //We create a pseudo-random permutation of the problem indexes
275  std::vector<population::size_type> shuffle(NP);
276  for(pagmo::population::size_type i=0; i < NP; ++i) {
277  shuffle[i] = i;
278  }
279  boost::uniform_int<int> pop_idx(0,NP-1);
280  boost::variate_generator<boost::mt19937 &, boost::uniform_int<int> > p_idx(m_urng,pop_idx);
281  std::random_shuffle(shuffle.begin(), shuffle.end(), p_idx);
282 
283  //We assign each problem to the individual which has minimum fitness on that problem
284  //This allows greater performance .... check without on dtlz2 for example.
285  std::vector<int> assignation_list(NP); //problem i is assigned to the individual assignation_list[i]
286  std::vector<bool> selected_list(NP,false); //keep track of the individuals already assigned to a problem
287  fitness_vector dec_fit(1); //temporary stores the decomposed fitness
288  for(pagmo::population::size_type i=0; i<NP;++i) { //for each problem i, select an individual j
289  unsigned int j = 0;
290  while(selected_list[j]) j++; //get to the first not already selected individual
291 
292  dynamic_cast<const pagmo::problem::decompose &>(*problems_vector[shuffle[i]]).compute_decomposed_fitness(dec_fit, pop.get_individual(j).cur_f);
293  double minFit = dec_fit[0];
294  int minFitPos = j;
295 
296  for(;j < NP; ++j) { //find the minimum fitness individual for problem i
297  if(!selected_list[j]) { //just consider individuals which have not been selected already
298  dynamic_cast<const pagmo::problem::decompose &>(*problems_vector[shuffle[i]]).compute_decomposed_fitness(dec_fit, pop.get_individual(j).cur_f);
299  if(dec_fit[0] < minFit) {
300  minFit = dec_fit[0];
301  minFitPos = j;
302  }
303  }
304  }
305  assignation_list[shuffle[i]] = minFitPos;
306  selected_list[minFitPos] = true;
307  }
308 
309  for(pagmo::population::size_type i=0; i<NP;++i) { //for each island/problem i
310  pagmo::population decomposed_pop(*problems_vector[i], 0, m_urng()); //Create a population for each decomposed problem
311 
312  //Set the individuals of the new population as one individual of the original population
313  // (according to assignation_list) plus m_T neighbours individuals
314  if(m_T < NP-1) {
315  decomposed_pop.push_back(X[assignation_list[i]]); //assign to the island the correct individual according to the assignation list
316  for(pagmo::population::size_type j = 1; j <= m_T; ++j) { //add the neighbours
317  decomposed_pop.push_back(X[assignation_list[indices[i][j]]]); //add the individual assigned to the island indices[i][j]
318  }
319  } else { //complete topology
320  for(pagmo::population::size_type j = 0 ; j < NP; ++j) {
321  decomposed_pop.push_back(X[j]);
322  }
323  }
324  arch.push_back(pagmo::island(*m_solver,decomposed_pop, selection_policy, replacement_policy));
325  }
326 
327  topology::custom topo;
328  if(m_T >= NP-1) {
329  topo = topology::fully_connected();
330  } else {
331  for(unsigned int i = 0; i < NP; ++i) {
332  topo.push_back();
333  }
334  for(unsigned int i = 0; i < NP; ++i) { //connect each island with the T closest neighbours
335  for(unsigned int j = 1; j <= m_T; ++j) { //start from 1 to avoid to connect with itself
336  topo.add_edge(i,indices[i][j]);
337  }
338  }
339  }
340  arch.set_topology(topo);
341 
342 
343  //Evolve the archipelago for m_gen generations
344  if(m_threads >= NP) { //asynchronous island evolution
345  arch.evolve(m_gen);
346  arch.join();
347  } else {
348  for(int g = 0; g < m_gen; ++g) { //batched island evolution
349  arch.evolve_batch(1, m_threads);
350  }
351  }
352 
353  // Finally, we assemble the evolved population selecting from the original one + the evolved one
354  // the best NP (crowding distance)
355  population popnew(pop);
356  for(pagmo::population::size_type i=0; i<arch.get_size() ;++i) {
357  popnew.push_back(arch.get_island(i)->get_population().champion().x);
358  }
359  std::vector<population::size_type> selected_idx = popnew.get_best_idx(NP);
360  // We completely clear the population (NOTE: memory of all individuals and the notion of
361  // champion is thus destroyed)
362  pop.clear();
363  // And we recreate it with the best NP among the evolved and the new population
364  for (population::size_type i=0; i < NP; ++i) {
365  pop.push_back(popnew.get_individual(selected_idx[i]).cur_x);
366  }
367 }
368 
370 std::string pade::get_name() const
371 {
372  return "Parallel Decomposition (PaDe)";
373 }
374 
376 
379 std::string pade::human_readable_extra() const
380 {
381  std::ostringstream s;
382  s << "gen:" << m_gen << ' ';
383  s << "threads:" << m_threads << ' ';
384  s << "solver:" << m_solver->get_name() << ' ';
385  s << "neighbours:" << m_T << ' ';
386  s << "decomposition:";
387  switch (m_method)
388  {
389  case pagmo::problem::decompose::BI : s << "BI" << ' ';
390  break;
391  case pagmo::problem::decompose::WEIGHTED : s << "WEIGHTED" << ' ';
392  break;
393  case pagmo::problem::decompose::TCHEBYCHEFF : s << "TCHEBYCHEFF" << ' ';
394  break;
395  }
396  s << "weights:";
397  switch (m_weight_generation)
398  {
399  case RANDOM : s << "RANDOM" << ' ';
400  break;
401  case LOW_DISCREPANCY : s << "LOW_DISCREPANCY" << ' ';
402  break;
403  case GRID : s << "GRID" << ' ';
404  break;
405  }
406  s << "ref. point" << m_z;
407  return s.str();
408 }
409 
410 }} //namespaces
411 
412 BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::algorithm::pade)
boost::shared_ptr< base > base_ptr
Alias for shared pointer to base algorithm.
Root PaGMO namespace.
std::vector< double > decision_vector
Decision vector type.
Definition: types.h:40
Halton sequence projected on a simplex.
Definition: discrepancy.h:176
Weights are generated on the simplex with low-discrepancy.
Definition: pade.h:59
fitness_vector cur_f
Current fitness vector.
Definition: population.h:89
Decompose meta-problem.
Definition: decompose.h:62
std::string human_readable_extra() const
Extra human readable algorithm info.
Definition: pade.cpp:379
const individual_type & get_individual(const size_type &) const
Get constant reference to individual at position n.
Definition: population.cpp:277
Custom topology.
Definition: custom.h:44
Base algorithm class.
Local island class.
Definition: island.h:68
size_type get_size() const
Get the size of the archipelago.
Base problem class.
Definition: problem/base.h:148
Population class.
Definition: population.h:70
void evolve(population &) const
Evolve implementation.
Definition: pade.cpp:217
The Boundary Intersection method is used to perform the decomposition.
Definition: decompose.h:69
Worst replacement policy.
"Choose best" migration selection policy.
Definition: best_s_policy.h:48
Parallel Decomposition (PaDe)
Definition: pade.h:52
void add_edge(int, int, double=1.0)
Add an edge.
Definition: custom.cpp:57
base_ptr clone() const
Clone method.
Definition: pade.cpp:104
std::vector< fitness_vector > generate_weights(const unsigned int, const unsigned int) const
Generates the weights used in the problem decomposition.
Definition: pade.cpp:140
base_island_ptr get_island(const size_type &) const
Island getter.
pade(int gen=10, unsigned int max_parallelism=1, pagmo::problem::decompose::method_type=pagmo::problem::decompose::BI, const pagmo::algorithm::base &=pagmo::algorithm::jde(100), population::size_type=8, weight_generation_type=LOW_DISCREPANCY, const fitness_vector &=std::vector< double >())
Constructor.
Definition: pade.cpp:69
Weights are generated on a uniform grid layed down on the simplex.
Definition: pade.h:58
void push_back(const base_island &)
Add an island to the archipelago.
void set_seeds(unsigned int)
Sets the seed of the random number generators of the archipelago.
void join() const
Wait until evolution on each island has terminated.
Individuals migrate to all the island's neighbours.
Definition: archipelago.h:80
std::vector< double > fitness_vector
Fitness vector type.
Definition: types.h:42
void evolve_batch(int, unsigned int, bool=true)
Run the evolution for the given number of iterations in batches.
void evolve(int=1)
Run the evolution for the given number of iterations.
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 > > &)
void set_topology(const topology::base &)
Set topology.
Weights are generated uniformly at random on the simplex.
Definition: pade.h:57
decision_vector cur_x
Current decision vector.
Definition: population.h:83
method_type
Mechanism used to perform the problem decomposition.
Definition: decompose.h:66
rng_uint32 m_urng
Random number generator for unsigned integer values.
f_size_type get_f_dimension() const
Return fitness dimension.
The fitness function is the weighted sum of the multiple original fitnesses.
Definition: decompose.h:67
weight_generation_type
Mechanism used to generate the weight vectors.
Definition: pade.h:56
void push_back()
Push back vertex.
Fully-connected topology.
Archipelago class.
Definition: archipelago.h:57
The Tchebycheff method is used to perform the decomposition.
Definition: decompose.h:68
std::string get_name() const
Algorithm name.
Definition: pade.cpp:370
rng_double m_drng
Random number generator for double-precision floating point values.