libfgen
0.1.15
Library for optimization using a genetic algorithm or particle swarm optimization
|
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