PaGMO  1.1.5
pso.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 #include <boost/random/uniform_int.hpp>
26 #include <boost/random/uniform_real.hpp>
27 #include <vector>
28 #include <cmath>
29 #include <iostream>
30 
31 #include "pso.h"
32 
33 namespace pagmo { namespace algorithm {
34 
36 
54 pso::pso(int gen, double omega, double eta1, double eta2, double vcoeff, int variant, int neighb_type, int neighb_param):base(),m_gen(gen),m_omega(omega),m_eta1(eta1),m_eta2(eta2),m_vcoeff(vcoeff),m_variant(variant),m_neighb_type(neighb_type),m_neighb_param(neighb_param) {
55  if (gen < 0) {
56  pagmo_throw(value_error,"number of generations must be nonnegative");
57  }
58 
59  if (m_omega < 0 || m_omega > 1) {
60  if( variant < 5 )
61  // variants using Inertia weight
62  pagmo_throw(value_error,"the particles' inertia must be in the [0,1] range");
63  else
64  // variants using Constriction coefficient
65  pagmo_throw(value_error,"the particles' constriction coefficient must be in the [0,1] range");
66  }
67 
68  if (eta1 < 0 || eta2 < 0 || eta1 > 4 || eta2 > 4) {
69  pagmo_throw(value_error,"the eta parameters must be in the [0,4] range");
70  }
71 
72  if (vcoeff <= 0 || vcoeff > 1) {
73  pagmo_throw(value_error,"fraction of variables' range in which velocity may vary should be in ]0,1]");
74  }
75 
76  if (variant < 1 || variant > 6) {
77  pagmo_throw(value_error,"algorithm variant must be one of 1 ... 6");
78  }
79 
80  if (neighb_type < 1 || neighb_type > 4) {
81  pagmo_throw(value_error,"swarm topology variant must be one of 1 ... 4");
82  }
83  // And neighb param???
84 }
85 
86 
89 {
90  return base_ptr(new pso(*this));
91 }
92 
93 
95 
100 void pso::evolve(population &pop) const
101 {
102  // Let's store some useful variables.
103  const problem::base &prob = pop.problem();
104  const problem::base::size_type D = prob.get_dimension(), prob_i_dimension = prob.get_i_dimension(), prob_c_dimension = prob.get_c_dimension(), prob_f_dimension = prob.get_f_dimension();
105  const problem::base::size_type Dc = D - prob_i_dimension;
106  const decision_vector &lb = prob.get_lb(), &ub = prob.get_ub();
107  const population::size_type swarm_size = pop.size();
108 
109 
110  //We perform some checks to determine wether the problem/population are suitable for PSO
111  if( Dc == 0 ){
112  pagmo_throw(value_error,"There is no continuous part in the problem decision vector for PSO to optimise");
113  }
114 
115  if( prob_c_dimension != 0 ){
116  pagmo_throw(value_error,"The problem is not box constrained and PSO is not suitable to solve it");
117  }
118 
119  if( prob_f_dimension != 1 ){
120  pagmo_throw(value_error,"The problem is not single objective and PSO is not suitable to solve it");
121  }
122 
123  // Get out if there is nothing to do.
124  if (swarm_size == 0 || m_gen == 0) {
125  return;
126  }
127 
128  // Some vectors used during evolution are allocated here.
129  std::vector<double> dummy(Dc,0); // used for initialisation purposes
130 
131  std::vector<decision_vector> X(swarm_size,dummy); // particles' current positions
132  std::vector<fitness_vector> fit(swarm_size); // particles' current fitness values
133 
134  std::vector<decision_vector > V(swarm_size,dummy); // particles' velocities
135 
136  std::vector<decision_vector> lbX(swarm_size,dummy); // particles' previous best positions
137  std::vector<fitness_vector> lbfit(swarm_size); // particles' fitness values at their previous best positions
138 
139  std::vector< std::vector<int> > neighb(swarm_size); // swarm topology (iterators over indexes of each particle's neighbors in the swarm)
140 
141  decision_vector best_neighb(Dc); // search space position of particles' best neighbor
142  fitness_vector best_fit; // fitness at the best found search space position (tracked only when using topologies 1 or 4)
143  bool best_fit_improved; // flag indicating whether the best solution's fitness improved (tracked only when using topologies 1 or 4)
144 
145 
146  decision_vector minv(Dc), maxv(Dc); // Maximum and minimum velocity allowed
147 
148  double vwidth; // Temporary variable
149  double new_x; // Temporary variable
150 
151  population::size_type p; // for iterating over particles
152  population::size_type n; // for iterating over particles's neighbours
153  problem::base::size_type d; // for iterating over problem dimensions
154 
155 
156  // Initialise the minimum and maximum velocity
157  for( d = 0; d < Dc; d++ ){
158  vwidth = ( ub[d] - lb[d] ) * m_vcoeff;
159  minv[d] = -1.0 * vwidth;
160  maxv[d] = vwidth;
161  }
162 
163  // Copy the particle positions, their velocities and their fitness
164  for( p = 0; p < swarm_size; p++ ){
165  X[p] = pop.get_individual(p).cur_x;
166  V[p] = pop.get_individual(p).cur_v;
167  fit[p] = pop.get_individual(p).cur_f;
168  }
169 
170  // Initialise particles' previous best positions
171  for( p = 0; p < swarm_size; p++ ){
172  lbX[p] = pop.get_individual(p).best_x;
173  lbfit[p] = pop.get_individual(p).best_f;
174  }
175 
176  // Initialize the Swarm's topology
177  switch( m_neighb_type ){
178  case 1: initialize_topology__gbest( pop, best_neighb, best_fit, neighb ); break;
179  case 3: initialize_topology__von( neighb ); break;
180  case 4: initialize_topology__adaptive_random( neighb );
181  best_fit = pop.champion().f; // need to track improvements in best found fitness, to know when to rewire
182  break;
183  case 2:
184  default: initialize_topology__lbest( neighb );
185  }
186 
187 
188  // auxiliary varibables specific to the Fully Informed Particle Swarm variant
189  double acceleration_coefficient = m_eta1 + m_eta2;
190  double sum_forces;
191 
192  double r1 = 0.0;
193  double r2 = 0.0;
194 
195  /* --- Main PSO loop ---
196  */
197  // For each generation
198  for( int g = 0; g < m_gen; ++g ){
199 
200  best_fit_improved = false;
201 
202  // For each particle in the swarm
203  for( p = 0; p < swarm_size; p++ ){
204 
205  // identify the current particle's best neighbour
206  // . not needed if m_neighb_type == 1 (gbest): best_neighb directly tracked in this function
207  // . not needed if m_variant == 6 (FIPS): all neighbours are considered, no need to identify the best one
208  if( m_neighb_type != 1 && m_variant != 6)
209  best_neighb = particle__get_best_neighbor( p, neighb, lbX, lbfit, prob );
210 
211 
212  /*-------PSO canonical (with inertia weight) ---------------------------------------------*/
213  /*-------Original algorithm used in PaGMO paper-------------------------------------------*/
214  if( m_variant == 1 ){
215  for( d = 0; d < Dc; d++ ){
216  r1 = m_drng();
217  r2 = m_drng();
218  V[p][d] = m_omega * V[p][d] + m_eta1 * r1 * (lbX[p][d] - X[p][d]) + m_eta2 * r2 * (best_neighb[d] - X[p][d]);
219  }
220  }
221 
222  /*-------PSO canonical (with inertia weight) ---------------------------------------------*/
223  /*-------and with equal random weights of social and cognitive components-----------------*/
224  /*-------Check with Rastrigin-------------------------------------------------------------*/
225  else if( m_variant == 2 ){
226  for( d = 0; d < Dc; d++ ){
227  r1 = m_drng();
228  V[p][d] = m_omega * V[p][d] + m_eta1 * r1 * (lbX[p][d] - X[p][d]) + m_eta2 * r1 * (best_neighb[d] - X[p][d]);
229  }
230  }
231 
232  /*-------PSO variant (commonly mistaken in literature for the canonical)----------------*/
233  /*-------Same random number for all components------------------------------------------*/
234  else if( m_variant == 3 ){
235  r1 = m_drng();
236  r2 = m_drng();
237  for( d = 0; d < Dc; d++ ){
238  V[p][d] = m_omega * V[p][d] + m_eta1 * r1 * (lbX[p][d] - X[p][d]) + m_eta2 * r2 * (best_neighb[d] - X[p][d]);
239  }
240  }
241 
242  /*-------PSO variant (commonly mistaken in literature for the canonical)----------------*/
243  /*-------Same random number for all components------------------------------------------*/
244  /*-------and with equal random weights of social and cognitive components---------------*/
245  else if( m_variant == 4 ){
246  r1 = m_drng();
247  for( d = 0; d < Dc; d++ ){
248  V[p][d] = m_omega * V[p][d] + m_eta1 * r1 * (lbX[p][d] - X[p][d]) + m_eta2 * r1 * (best_neighb[d] - X[p][d]);
249  }
250  }
251 
252  /*-------PSO variant with constriction coefficients------------------------------------*/
253  /* ''Clerc's analysis of the iterative system led him to propose a strategy for the
254  * placement of "constriction coefficients" on the terms of the formulas; these
255  * coefficients controlled the convergence of the particle and allowed an elegant and
256  * well-explained method for preventing explosion, ensuring convergence, and
257  * eliminating the arbitrary Vmax parameter. The analysis also takes the guesswork
258  * out of setting the values of phi_1 and phi_2.''
259  * ''this is the canonical particle swarm algorithm of today.''
260  * [Poli et al., 2007] http://dx.doi.org/10.1007/s11721-007-0002-0
261  * [Clerc and Kennedy, 2002] http://dx.doi.org/10.1109/4235.985692
262  *
263  * This being the canonical PSO of today, this variant is set as the default in PaGMO.
264  *-------------------------------------------------------------------------------------*/
265  else if( m_variant == 5 ){
266  for( d = 0; d < Dc; d++ ){
267  r1 = m_drng();
268  r2 = m_drng();
269  V[p][d] = m_omega * ( V[p][d] + m_eta1 * r1 * (lbX[p][d] - X[p][d]) + m_eta2 * r2 * (best_neighb[d] - X[p][d]) );
270  }
271  }
272 
273  /*-------Fully Informed Particle Swarm-------------------------------------------------*/
274  /* ''Whereas in the traditional algorithm each particle is affected by its own
275  * previous performance and the single best success found in its neighborhood, in
276  * Mendes' fully informed particle swarm (FIPS), the particle is affected by all its
277  * neighbors, sometimes with no influence from its own previous success.''
278  * ''With good parameters, FIPS appears to find better solutions in fewer iterations
279  * than the canonical algorithm, but it is much more dependent on the population topology.''
280  * [Poli et al., 2007] http://dx.doi.org/10.1007/s11721-007-0002-0
281  * [Mendes et al., 2004] http://dx.doi.org/10.1109/TEVC.2004.826074
282  *-------------------------------------------------------------------------------------*/
283  else if( m_variant == 6 ){
284  for( d = 0; d < Dc; d++ ){
285  sum_forces = 0.0;
286  for( n = 0; n < neighb[p].size(); n++ )
287  sum_forces += m_drng() * acceleration_coefficient * ( lbX[ neighb[p][n] ][d] - X[p][d] );
288 
289  V[p][d] = m_omega * ( V[p][d] + sum_forces / neighb[p].size() );
290  }
291  }
292 
293  // We now check that the velocity does not exceed the maximum allowed per component
294  // and we perform the position update and the feasibility correction
295  for( d = 0; d < Dc; d++ ){
296 
297  if( V[p][d] > maxv[d] )
298  V[p][d] = maxv[d];
299 
300  else if( V[p][d] < minv[d] )
301  V[p][d] = minv[d];
302 
303  // update position
304  new_x = X[p][d] + V[p][d];
305 
306  // feasibility correction
307  // (velocity updated to that which would have taken the previous position
308  // to the newly corrected feasible position)
309  if( new_x < lb[d] ){
310  new_x = lb[d];
311  V[p][d] = 0.0;
312 // new_x = boost::uniform_real<double>(lb[d],ub[d])(m_drng);
313 // V[p][d] = new_x - X[p][d];
314  }
315  else if( new_x > ub[d] ){
316  new_x = ub[d];
317  V[p][d] = 0.0;
318 // new_x = boost::uniform_real<double>(lb[d],ub[d])(m_drng);
319 // V[p][d] = new_x - X[p][d];
320  }
321 
322  X[p][d] = new_x;
323  }
324 
325  // We evaluate here the new individual fitness as to be able to update the global best in real time
326  prob.objfun( fit[p], X[p] );
327  m_fevals++;
328 
329  if( prob.compare_fitness( fit[p], lbfit[p] ) ){
330  // update the particle's previous best position
331  lbfit[p] = fit[p];
332  lbX[p] = X[p];
333 
334  // update the best position observed so far by any particle in the swarm
335  // (only performed if swarm topology is gbest)
336  if( ( m_neighb_type == 1 || m_neighb_type == 4 ) && prob.compare_fitness( fit[p], best_fit ) ){
337  best_neighb = X[p];
338  best_fit = fit[p];
339  best_fit_improved = true;
340  }
341  }
342 
343  } //End of loop on the population members
344 
345  // reset swarm topology if no improvement was observed in the best found fitness value
346  if( m_neighb_type == 4 && !best_fit_improved )
348 
349  } // end of main PSO loop
350 
351 
352  // copy particles' positions & velocities back to the main population
353  for( p = 0; p < swarm_size; p++ ){
354  pop.set_x( p, lbX[p] ); // sets: cur_x, cur_f, best_x, best_f
355  pop.set_x( p, X[p] ); // sets: cur_x, cur_f
356  pop.set_v( p, V[p] ); // sets: cur_v
357  }
358 }
359 
360 
371 decision_vector pso::particle__get_best_neighbor( population::size_type pidx, std::vector< std::vector<int> > &neighb, const std::vector<decision_vector> &lbX, const std::vector<fitness_vector> &lbfit, const problem::base &prob ) const
372 {
373  population::size_type nidx, bnidx; // neighbour index; best neighbour index
374 
375  switch( m_neighb_type ){
376  case 1: // { gbest }
377  // ERROR: execution should not reach this point, as the global best position is not tracked using the neighb vector
378  pagmo_throw(value_error,"particle__get_best_neighbor() invoked while using a gbest swarm topology");
379  break;
380  case 2: // { lbest }
381  case 3: // { von }
382  case 4: // { adaptive random }
383  default:
384  // iterate over indexes of the particle's neighbours, and identify the best
385  bnidx = neighb[pidx][0];
386  for( nidx = 1; nidx < neighb[pidx].size(); nidx++ )
387  if( prob.compare_fitness( lbfit[ neighb[pidx][nidx] ], lbfit[ bnidx ] ) )
388  bnidx = neighb[pidx][nidx];
389  return lbX[bnidx];
390  }
391 }
392 
393 
413 void pso::initialize_topology__gbest( const population &pop, decision_vector &gbX, fitness_vector &gbfit, std::vector< std::vector<int> > &neighb ) const
414 {
415  // The best position already visited by the swarm will be tracked in pso::evolve() as particles are evaluated.
416  // Here we define the initial values of the variables that will do that tracking.
417  gbX = pop.champion().x;
418  gbfit = pop.champion().f;
419 
420  /* The usage of a gbest swarm topology along with a FIPS (fully informed particle swarm) velocity update formula
421  * is discouraged. However, because a user might still configure such a setup, we must ensure FIPS has access to
422  * the list of indices of particles' neighbours:
423  */
424  if( m_variant == 6 ){
425  unsigned int i;
426  for( i = 0; i < neighb.size(); i++ )
427  neighb[0].push_back( i );
428  for( i = 1; i < neighb.size(); i++ )
429  neighb[i] = neighb[0];
430  }
431 }
432 
433 
446 void pso::initialize_topology__lbest( std::vector< std::vector<int> > &neighb ) const
447 {
448  int swarm_size = neighb.size();
449  int pidx; // for iterating over particles
450  int nidx, j; // for iterating over particles' neighbours
451 
452  int radius = m_neighb_param / 2;
453 
454  for( pidx = 0; pidx < swarm_size; pidx++ ){
455  for( j = -radius; j <= radius; j++ ){
456  if( j == 0 ) j++;
457  nidx = (pidx + j) % swarm_size;
458  if( nidx < 0 ) nidx = swarm_size + nidx;
459  neighb[pidx].push_back( nidx );
460  }
461  }
462 }
463 
464 
474 const int vonNeumann_neighb_diff[4][2] = { {-1,0}, {1,0}, {0,-1}, {0,1} };
475 
493 void pso::initialize_topology__von( std::vector< std::vector<int> > &neighb ) const
494 {
495  int swarm_size = neighb.size();
496  int cols, rows; // lattice structure
497  int pidx, nidx; // particle and neighbour indices, in the swarm and neighbourhood vectors
498  int p_x, p_y; // particle's coordinates in the lattice
499  int n_x, n_y; // particle neighbor's coordinates in the lattice
500 
501  rows = std::sqrt( swarm_size );
502  while( swarm_size % rows != 0 )
503  rows -= 1;
504  cols = swarm_size / rows;
505 
506  for( pidx = 0; pidx < swarm_size; pidx++ ){
507  p_x = pidx % cols;
508  p_y = pidx / cols;
509 
510  for( nidx = 0; nidx < 4; nidx++ ){
511  n_x = ( p_x + vonNeumann_neighb_diff[nidx][0] ) % cols; if( n_x < 0 ) n_x = cols + n_x; // sign of remainder(%) in a division when at least one of the operands is negative is compiler implementation specific. The 'if' here ensures the same behaviour across compilers
512  n_y = ( p_y + vonNeumann_neighb_diff[nidx][1] ) % rows; if( n_y < 0 ) n_y = rows + n_y;
513 
514  neighb[pidx].push_back( n_y * cols + n_x );
515  }
516  }
517 }
518 
519 
542 void pso::initialize_topology__adaptive_random( std::vector< std::vector<int> > &neighb ) const
543 {
544  int swarm_size = neighb.size();
545  int pidx; // for iterating over particles
546  int nidx, j; // for iterating over particles being connected to
547 
548  // clear previously defined topology
549  for( pidx = 0; pidx < swarm_size; pidx++ )
550  neighb[pidx].clear();
551 
552  // define new topology
553  for( pidx = 0; pidx < swarm_size; pidx++ ){
554 
555  // the particle always connects back to itself, thus guaranteeing a minimum indegree of 1
556  neighb[pidx].push_back( pidx );
557 
558  for( j = 1; j < m_neighb_param; j++ ){
559  nidx = m_drng() * swarm_size;
560  neighb[nidx].push_back( pidx );
561  // No check performed to see whether pidx is already in neighb[nidx],
562  // leading to a performance penalty in particle__get_best_neighbor() when it occurs.
563  }
564  }
565 }
566 
567 
568 
570 std::string pso::get_name() const
571 {
572  return "Particle Swarm optimization";
573 }
574 
575 
577 
580 std::string pso::human_readable_extra() const
581 {
582  std::ostringstream s;
583  s << "gen:" << m_gen << ' ';
584  s << "omega:" << m_omega << ' ';
585  s << "eta1:" << m_eta1 << ' ';
586  s << "eta2:" << m_eta2 << ' ';
587  s << "variant:" << m_variant << ' ';
588  s << "topology:" << m_neighb_type << ' ';
589  if( m_neighb_type == 2 || m_neighb_type == 4 )
590  s << "topology param.:" << m_neighb_param << ' ';
591  return s.str();
592 }
593 
594 }} //namespaces
595 
596 BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::algorithm::pso)
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
fitness_vector cur_f
Current fitness vector.
Definition: population.h:89
const individual_type & get_individual(const size_type &) const
Get constant reference to individual at position n.
Definition: population.cpp:277
Particle Swarm optimization.
Definition: pso.h:75
void initialize_topology__lbest(std::vector< std::vector< int > > &neighb) const
Defines the Swarm topology as a ring, where particles are influenced by their immediate neighbors to ...
Definition: pso.cpp:446
Base algorithm class.
const int vonNeumann_neighb_diff[4][2]
Von Neumann neighborhood (increments on particles' lattice coordinates that produce the coordinates o...
Definition: pso.cpp:474
void initialize_topology__von(std::vector< std::vector< int > > &neighb) const
Arranges particles in a lattice, where each interacts with its immediate 4 neighbors to the N...
Definition: pso.cpp:493
Base problem class.
Definition: problem/base.h:148
std::string human_readable_extra() const
Extra human readable algorithm info.
Definition: pso.cpp:580
Population class.
Definition: population.h:70
void initialize_topology__gbest(const population &pop, decision_vector &gbX, fitness_vector &gbfit, std::vector< std::vector< int > > &neighb) const
Defines the Swarm topology as a fully connected graph, where particles are influenced by all other pa...
Definition: pso.cpp:413
size_type get_dimension() const
Return global dimension.
decision_vector x
Decision vector.
Definition: population.h:149
bool compare_fitness(const fitness_vector &, const fitness_vector &) const
Compare fitness vectors.
decision_vector particle__get_best_neighbor(population::size_type pidx, std::vector< std::vector< int > > &neighb, const std::vector< decision_vector > &lbX, const std::vector< fitness_vector > &lbfit, const problem::base &prob) const
Get information on the best position already visited by any of a particle's neighbours.
Definition: pso.cpp:371
fitness_vector f
Fitness vector.
Definition: population.h:153
fitness_vector objfun(const decision_vector &) const
Return fitness of pagmo::decision_vector.
unsigned int m_fevals
A counter for the number of function evaluations.
size_type get_i_dimension() const
Return integer dimension.
base_ptr clone() const
Clone method.
Definition: pso.cpp:88
void initialize_topology__adaptive_random(std::vector< std::vector< int > > &neighb) const
Arranges particles in a random graph having a parameterized maximum outdegree; the graph changes adap...
Definition: pso.cpp:542
fitness_vector best_f
Best fitness vector so far.
Definition: population.h:95
std::vector< double > fitness_vector
Fitness vector type.
Definition: types.h:42
std::string get_name() const
Algorithm name.
Definition: pso.cpp:570
c_size_type get_c_dimension() const
Return global constraints dimension.
decision_vector cur_v
Current velocity vector.
Definition: population.h:85
const decision_vector & get_ub() const
Upper bounds getter.
pso(int gen=1, double omega=0.7298, double eta1=2.05, double eta2=2.05, double vcoeff=0.5, int variant=5, int neighb_type=2, int neighb_param=4)
Constructor.
Definition: pso.cpp:54
container_type::size_type size_type
Population size type.
Definition: population.h:192
decision_vector cur_x
Current decision vector.
Definition: population.h:83
f_size_type get_f_dimension() const
Return fitness dimension.
const decision_vector & get_lb() const
Lower bounds getter.
rng_double m_drng
Random number generator for double-precision floating point values.
void evolve(population &) const
Evolve implementation.
Definition: pso.cpp:100
decision_vector best_x
Best decision vector so far.
Definition: population.h:91
decision_vector::size_type size_type
Problem's size type: the same as pagmo::decision_vector's size type.
Definition: problem/base.h:160