libfgen  0.1.15
Library for optimization using a genetic algorithm or particle swarm optimization
selection.c
00001 /*
00002     selection.c -- Selection stage of the genetic algorithm.
00003 
00004     fgen -- Library for optimization using a genetic algorithm or particle swarm optimization.
00005     Copyright 2012, Harm Hanemaaijer
00006 
00007     This file is part of fgen.
00008 
00009     fgen is free software: you can redistribute it and/or modify it
00010     under the terms of the GNU Lesser General Public License as published
00011     by the Free Software Foundation, either version 3 of the License, or
00012     (at your option) any later version.
00013 
00014     fgen is distributed in the hope that it will be useful, but WITHOUT
00015     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00016     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
00017     License for more details.
00018 
00019     You should have received a copy of the GNU Lesser General Public
00020     License along with fgen.  If not, see <http://www.gnu.org/licenses/>.
00021 
00022 */
00023 
00024 #include <stdlib.h>
00025 #include <stdio.h>
00026 #include <math.h>
00027 #include <limits.h>
00028 #include <float.h>
00029 #include "fgen.h"
00030 #include "parameters.h"
00031 #include "population.h"
00032 #include "random.h"
00033 #include "error.h"
00034 #include "win32_compat.h"
00035 
00036 static int CompareFitness(const void *p1, const void *p2) {
00037         FgenIndividual *q1 = *(FgenIndividual **)p1;
00038         FgenIndividual *q2 = *(FgenIndividual **)p2;
00039         if (q1->fitness > q2->fitness)
00040             return -1;
00041         if (q1->fitness < q2->fitness)
00042             return 1;
00043         return 0;
00044 }
00045 
00046 /* In elitist selection, select the best individuals beforehand. They will also be allowed */
00047 /* to be selected extra times based on fitness. */
00048 
00049 static int DoElitism(FgenPopulation *pop, int *random_order, FgenIndividual **ind1, FgenIndividual **ind2) {
00050         if (!(pop->selection_type & FGEN_ELITIST_ELEMENT))
00051                 return 0;
00052         for (int i = 0; i < pop->size; i++)
00053                 ind1[i]->is_elite = 0;
00054         // During ranked-based selection, the population was already sorted.
00055         if (!((pop->selection_type & FGEN_STOCHASTIC_TYPE_MASK) == FGEN_RANK))
00056                 // Sort the population on fitness.
00057                 qsort(ind1, pop->size, sizeof(ind1[0]), CompareFitness);
00058         for (int i = 0; i < pop->nu_elites; i++) {
00059                 // Have to mostly create new individuals to avoid problems with propagating
00060                 // the is_elite == true characteristic to other individuals that share
00061                 // the same data structure.
00062                 // However, if the current elite is identical to the previous one, don't create a new
00063                 // individual.
00064                 if (i > 0)
00065                         if (ind1[i] == ind1[i - 1]) {
00066                                 ind2[random_order[i]] = ind2[random_order[i - 1]];
00067                                 ind2[random_order[i - 1]]->refcount++;
00068                                 continue;
00069                         }
00070                 ind2[random_order[i]] = NewIndividual(pop);
00071                 CopyIndividualBitstring(pop, ind1[i], ind2[random_order[i]]);
00072                 ind2[random_order[i]]->fitness = ind1[i]->fitness;
00073                 ind2[random_order[i]]->fitness_is_valid = 1;
00074                 ind2[random_order[i]]->is_elite = 1;
00075         }
00076         return pop->nu_elites;
00077 }
00078 
00079 
00080 /* Stochastic selection with replacement. */
00081 
00082 static void StochasticSelection(FgenPopulation *pop, FgenIndividual **ind1, FgenIndividual **ind2, double sum,
00083 double fitness_cut) {
00084         int i;
00085         int *random_order;
00086         /*
00087          * Inefficient roulette wheel selection.
00088          * This traverses the whole roulette wheel for each individual
00089          * selected. A more efficient way would be to pick individuals
00090          * corresponding to equally distant markers (pop->size in
00091          * number) along the roulette wheel (implemented in universal
00092          * stochastic sampling).
00093          */
00094 
00095         /* Calculate random order for target population. */
00096         random_order = (int *)malloc(sizeof(int) * pop->size);
00097         CalculateRandomOrder(pop->rng, random_order, pop->size);
00098 
00099         /* In elitist selection, select the best individuals beforehand. They will also be allowed */
00100         /* to be selected extra times based on fitness. */
00101         int nu_elites = DoElitism(pop, random_order, ind1, ind2);
00102 
00103         double sum2 = 0;
00104         for (i = 0; i < pop->size; i++)
00105             sum2 += ind1[i]->fitness - fitness_cut;
00106         for (i = nu_elites; i < pop->size; i++) {
00107                 int j;
00108                 double draw, csum;
00109                 /* Pick a spot in the cumulative sum. */
00110                 draw = fgen_random_d(pop->rng, sum2);
00111                 /* Find the individual. */
00112                 csum = 0;
00113                 for (j = 0; j < pop->size; j++) {
00114                         csum += ind1[j]->fitness - fitness_cut;
00115                         if (draw < csum)
00116                                 break;
00117                 }
00118                 /* Avoid rounding errors. */
00119                 if (j == pop->size) {
00120                         j = pop->size - 1;
00121                 }
00122                 ind2[random_order[i]] = ind1[j];
00123                 ind1[j]->refcount++;
00124         }
00125 
00126         free(random_order);
00127 }
00128 
00129 /* Stochastic Universal Sampling. */
00130 
00131 static void StochasticUniversalSamplingSelection(FgenPopulation *pop, FgenIndividual **ind1, FgenIndividual **ind2,
00132 double sum, double fitness_cut ) {
00133         int i, j;
00134         double csum, draw, step;
00135         double *pop_csum;
00136         int *random_order;
00137         /*
00138          * The individuals corresponding to equally distant markers
00139          * (pop->size in number) along the roulette wheel are picked.
00140          * This ensures that the each individual gets at least the integer
00141          * part of its expected number.
00142          *
00143          */
00144 
00145         /* Calculate random order for target population. */
00146         random_order = (int *)malloc(sizeof(int) * pop->size);
00147         CalculateRandomOrder(pop->rng, random_order, pop->size);
00148 
00149         /* In elitist selection, select the best individuals beforehand. They will also be allowed */
00150         /* to be selected extra times based on fitness. */
00151         int nu_elites = DoElitism(pop, random_order, ind1, ind2);
00152 
00153         /* Calculate array of cumulative sums. */
00154         pop_csum = (double *)malloc(sizeof(double) * pop->size);
00155         csum = 0;
00156         for (i = 0; i < pop->size; i++) {
00157                 csum += ind1[i]->fitness - fitness_cut;
00158                 pop_csum[i] = csum;
00159         }
00160 
00161         if (pop->selection_type & FGEN_ELITIST_ELEMENT)
00162                 step = csum / (pop->size - nu_elites);
00163         else
00164                 step = csum / pop->size;
00165         /* Pick a spot in the cumulative sum. */
00166         draw = fgen_random_d(pop->rng, csum);
00167 
00168         /* Traverse the roulette wheel with the calculated step. */
00169         /* Initially the starting spot will be looked for. */
00170         i = 0;
00171         for (j = nu_elites; j < pop->size; j++) {
00172                 for (;;) {
00173                         if (draw <= pop_csum[i])
00174                                 break;
00175                         i++;
00176                         if (i >= pop->size) {
00177                                 /* gen_error("StochasticUniversalSamplingSelection: Round problem?"); */
00178                                 /* This should almost never happen, but when it does, handle it gracefully. */
00179                                 i = pop->size - 1;
00180                                 break;
00181                         }
00182                 }
00183 
00184                 ind2[random_order[j]] = ind1[i];
00185                 ind1[i]->refcount++;
00186 
00187                 draw += step;
00188                 if (draw >= csum) {
00189                         /* Wrap around. */
00190                         draw -= csum;
00191                         i = 0;
00192                 }
00193         }
00194 
00195         free(pop_csum);
00196         free(random_order);
00197 }
00198 
00199 /*
00200  * Tournament selection.
00201  */
00202 
00203 static void TournamentSelection(FgenPopulation *pop, FgenIndividual **ind1, FgenIndividual **ind2) {
00204         int *random_order;
00205         int i;
00206         /* Calculate random order for target population. */
00207         random_order = (int *)malloc(sizeof(int) * pop->size);
00208         CalculateRandomOrder(pop->rng, random_order, pop->size);
00209 
00210         /* In elitist selection, select the best individuals beforehand. They will also be allowed */
00211         /* to be selected extra times based on fitness. */
00212         int nu_elites = DoElitism(pop, random_order, ind1, ind2);
00213 
00214         for (i = nu_elites; i < pop->size; i++) {
00215                 FgenIndividual *winner;
00216                 int j;
00217                 double best_fitness = NEGATIVE_INFINITY_DOUBLE;
00218                 for (j = 0; j < pop->tournament_size; j++) {
00219                         int k = fgen_random_n(pop->rng, pop->size);
00220                         if (ind1[k]->fitness > best_fitness) {
00221                                 winner = ind1[k];
00222                                 best_fitness = ind1[k]->fitness;
00223                         }
00224                 }
00225                 ind2[random_order[i]] = winner;
00226                 winner->refcount++;
00227         }
00228         free(random_order);
00229 }
00230 
00231 /*
00232  * Rank-based selection
00233  */
00234 
00235 static void RankSelection(FgenPopulation *pop, FgenIndividual **ind1, FgenIndividual **ind2) {
00236         int i, j;
00237         double csum, draw, step;
00238         double *pop_csum;
00239         int *random_order;
00240         /*
00241          * The individuals corresponding to equally distant markers
00242          * (pop->size in number) along the roulette wheel are picked.
00243          * This ensures that the each individual gets at least the integer
00244          * part of its expected number.
00245          */
00246 
00247         /* Calculate random order for target population. */
00248         random_order = (int *)malloc(sizeof(int) * pop->size);
00249         CalculateRandomOrder(pop->rng, random_order, pop->size);
00250 
00251         /* Sort the population on fitness. */
00252         qsort(ind1, pop->size, sizeof(ind1[0]), CompareFitness);
00253 
00254         /* In elitist selection, select the best individuals beforehand. They will also be allowed */
00255         /* to be selected extra times based on fitness. */
00256         int nu_elites = DoElitism(pop, random_order, ind1, ind2);
00257 
00258         /* Calculate array of cumulative sums. */
00259         pop_csum = (double *)malloc(sizeof(double) * pop->size);
00260         csum = 0;
00261         for (i = 0; i < pop->size; i++) {
00262                 // The fitness used for selection is proportional to the rank.
00263                 csum += pop->size - i;
00264                 pop_csum[i] = csum;
00265         }
00266 
00267         if (pop->selection_type & FGEN_ELITIST_ELEMENT)
00268                 step = csum / (pop->size - nu_elites);
00269         else
00270                 step = csum / pop->size;
00271         /* Pick a spot in the cumulative sum. */
00272         draw = fgen_random_d(pop->rng, csum);
00273 
00274         /* Traverse the roulette wheel with the calculated step. */
00275         /* Initially the starting spot will be looked for. */
00276         i = 0;
00277         for (j = nu_elites; j < pop->size; j++) {
00278                 for (;;) {
00279                         if (draw <= pop_csum[i])
00280                                 break;
00281                         i++;
00282                         if (i >= pop->size) {
00283                                 /* gen_error("RankSelection: Round problem?"); */
00284                                 /* This should almost never happen, but when it does, handle it gracefully. */
00285                                 i = pop->size - 1;
00286                                 break;
00287                         }
00288                 }
00289 
00290                 ind2[random_order[j]] = ind1[i];
00291                 ind1[i]->refcount++;
00292 
00293                 draw += step;
00294                 if (draw >= csum) {
00295                         /* Wrap around. */
00296                         draw -= csum;
00297                         i = 0;
00298                 }
00299         }
00300 
00301         free(pop_csum);
00302         free(random_order);
00303 }
00304 
00305 /*
00306  * Extinction
00307  */
00308 
00309 static void DoExtinction(FgenPopulation *pop, FgenIndividual **ind1, FgenIndividual **ind2) {
00310         int i;
00311         /* Create a population with a single copy of the best individual with the rest random new individuals. */
00312         FgenIndividual *best_ind = fgen_best_individual_of_population(pop);
00313         ind2[0] = NewIndividual(pop);
00314         CopyIndividualBitstring(pop, best_ind, ind2[0]);
00315         ind2[0]->fitness = best_ind->fitness;
00316         ind2[0]->fitness_is_valid = 1;
00317         ind2[0]->is_elite = 1;
00318         FreePopulation(pop, ind1);
00319         for (i = 1; i < pop->size; i++) {
00320                 ind2[i] = NewIndividual(pop);
00321                 pop->fgen_seed_func(pop, ind2[i]->bitstring);
00322         }
00323         free(ind1);
00324         pop->ind = ind2;
00325 }
00326 
00327 
00328 /*
00329  * Perform the selection step of the genetic algorithm.
00330  */
00331 
00332 void DoSelection(FgenPopulation *pop) {
00333         double sum, min_fitness, fitness_cut;
00334         FgenIndividual **ind1, **ind2;
00335 
00336         CalculatePopulationFitness(pop, &sum, &min_fitness);
00337 
00338         ind1 = pop->ind;
00339         ind2 = AllocatePopulation(pop);
00340 
00341         if (pop->selection_type & FGEN_EXTINCTION_ELEMENT) {
00342                 if (pop->generation > 0 && pop->generation % 200 == 0) {
00343                         DoExtinction(pop, ind1, ind2);
00344                         return;
00345                 }
00346         }
00347 
00348         /* If there is an individual with infinite fitness, copy it to the whole population. */
00349         if (sum == POSITIVE_INFINITY_DOUBLE) {
00350                 int i, j;
00351                 for (i = 0; i < pop->size; i++)
00352                         if (ind1[i]->fitness == POSITIVE_INFINITY_DOUBLE)
00353                                 break;
00354                 if (i == pop->size)
00355                         gen_error("fgen: DoSelection: sum of fitness is infinite, but infinite fitness not found.");
00356                 for (j = 0; j < pop->size; j++) {
00357                         ind2[j] = ind1[i];
00358                         ind1[i]->refcount++;
00359                 }
00360                 goto end;
00361         }
00362         if (isnan(sum) || sum == NEGATIVE_INFINITY_DOUBLE)
00363                 gen_error("fgen: DoSelection: fitness with NaN or negative infinity detected.");
00364         /* If the fitness of every individual is identical, copy the population unchanged. */
00365         int i;
00366         for (i = 0; i < pop->size - 1; i++)
00367                 if (ind1[i]->fitness != ind1[i + 1]->fitness)
00368                         break;
00369         if (i == pop->size - 1) {
00370                 int j;
00371                 for (j = 0; j < pop->size; j++) {
00372                         ind2[j] = ind1[j];
00373                         ind1[j]->refcount++;
00374                 }
00375                 goto end;
00376         }
00377 
00378         if (pop->selection_fitness_type == FGEN_FITNESS_PROPORTIONAL)
00379                 fitness_cut = 0;
00380         else
00381         if (pop->selection_fitness_type == FGEN_SUBTRACT_MIN_FITNESS)
00382                 fitness_cut = min_fitness;
00383         else
00384         if (pop->selection_fitness_type == FGEN_SUBTRACT_MIN_FITNESS_DIV_2)
00385                 fitness_cut = min_fitness / 2;
00386         else
00387                 gen_error("DoSelection: Unsupported selection type.");
00388 
00389         if ((pop->selection_type & FGEN_STOCHASTIC_TYPE_MASK) == FGEN_STOCHASTIC)
00390                 StochasticSelection(pop, ind1, ind2, sum, fitness_cut);
00391         else
00392         if ((pop->selection_type & FGEN_STOCHASTIC_TYPE_MASK) == FGEN_SUS)
00393                 StochasticUniversalSamplingSelection(pop, ind1, ind2, sum, fitness_cut);
00394         else
00395         if ((pop->selection_type & FGEN_STOCHASTIC_TYPE_MASK) == FGEN_TOURNAMENT)
00396                 TournamentSelection(pop, ind1, ind2);
00397         else
00398         if ((pop->selection_type & FGEN_STOCHASTIC_TYPE_MASK) == FGEN_RANK)
00399                 RankSelection(pop, ind1, ind2);
00400         else
00401                 gen_error("DoSelection: Unsupported selection stochastic type.");
00402 
00403 end : 
00404         FreePopulation(pop, ind1);
00405         free(ind1);
00406         pop->ind = ind2;
00407 }
00408 
 All Data Structures Functions Variables