-
Notifications
You must be signed in to change notification settings - Fork 0
/
genetic.c
453 lines (353 loc) · 14.6 KB
/
genetic.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include "datastructs.h"
#include "utility.h"
#include "visual.h"
#include "genetic.h"
/* Starts the genetic algorithm
- popsize : the number of chromosomes in the populations
- generations : how many iterations the algorithm should run
- mutationrate : how likely a mutations is to happen. Between [0..1]
Returns a pointer to an array of groups
*/
Group* genetic_algorithm(GASettings settings, DataSet data, int groupCount) {
FILE *lgf; /* Log file */
int gen;
Group *result;
/* Open log file */
lgf = fopen("genlog.csv", "w");
assert(lgf != NULL);
log_make_header(lgf, data, settings, groupCount);
/* Setting up array of Chromosomes by allocating memory.
Also generates first generation of random chromosomes */
Chromosome *population = genetic_generate_initial_population(settings.popsize, data, groupCount);
/* Similarly, allocate memory to generate a new population */
Chromosome *nextGeneration = genetic_get_empty_population(settings.popsize, data, groupCount);
printf("Population initialized...\n");
for (gen = 0; gen < settings.generations; gen++) {
int i;
Chromosome *tempPop;
/* Sort according to fitness */
qsort(population, settings.popsize, sizeof(Chromosome), genetic_q_compare);
/* Analyse how the algorithm is doing */
genetic_analyse(lgf, gen, population, settings.popsize);
/* Create new population */
for (i = 0; i < settings.popsize / 2; i++) {
Chromosome parent1, parent2, *child1, *child2;
/* Make child pointers point to the next positions in nextGeneration */
child1 = &(nextGeneration[2 * i]);
child2 = &(nextGeneration[2 * i + 1]);
/* Selection. Make parent1 and parent2 point to two chromosomes */
genetic_selection(population, settings.popsize, &parent1, &parent2);
/* Crossover. Merge parent1 and parent2 into two children */
genetic_crossover(parent1, parent2, child1, child2);
/* Mutation. A small chance to make a small change in the chromosomes */
genetic_mutation(child1, settings.mutationrate);
genetic_mutation(child2, settings.mutationrate);
}
/* If popsize is odd, we have to add another chromosome. We just
copy the one with highest fitness */
if (settings.popsize % 2 == 1) {
genetic_copy_chromosome(nextGeneration + settings.popsize - 1, population[0], data.personCount);
}
/* Set population to next generation, by swapping current and next */
tempPop = population;
population = nextGeneration;
nextGeneration = tempPop;
}
/* Sort according to fitness, then make the BEST chromosome into groups */
qsort(population, settings.popsize, sizeof(Chromosome), genetic_q_compare);
result = genetic_chromosome_to_groups(population[0]);
/* Calc the fitness in the final groups */
fitness_groups(result, groupCount, data.allCriteria, data.criteriaCount);
/* Clean up memory */
genetic_kill_population(population, settings.popsize);
genetic_kill_population(nextGeneration, settings.popsize);
/* Close log file */
fclose(lgf);
/* Return an array with groups */
return result;
}
/* Make the header of the log file */
void log_make_header(FILE *lgf, DataSet data, GASettings settings, int groupCount) {
fprintf(lgf, "Person Count:;%d\n", data.personCount);
fprintf(lgf, "Group Count:;%d\n", groupCount);
fprintf(lgf, "Population size:;%d\n", settings.popsize);
fprintf(lgf, "Generations:;%d\n", settings.generations);
fprintf(lgf, "Mutation rate:;%f\n", settings.mutationrate);
fprintf(lgf, "Generation;Avg;Median;Best;worst\n");
}
/* Analyse a generation. Calculates interesting numbers, prints some of them
but stores everything in the log file. Population must be sorted */
void genetic_analyse(FILE *lgf, int gen, Chromosome *population, int popsize) {
/* Calculate interesting numbers */
double avg = genetic_average_fitness(population, popsize);
double med = genetic_median_fitness(population, popsize);
double best = fitness_chromosome(population[0]);
double worst = fitness_chromosome(population[popsize - 1]);
/* Write the numbers into the log file */
fprintf(lgf, "%d;%lf;%lf;%lf;%lf\n", gen, avg, med, best, worst);
/* Show how the algorithm is doing every 10'th generation */
if (gen % 10 == 0)
print_generation(gen, avg, med, best, worst);
}
/* Returns the average fitness of the population of chromosomes */
double genetic_average_fitness(Chromosome *population, int popsize) {
int i;
double total = 0;
for (i = 0; i < popsize; i++) {
total += fitness_chromosome(population[i]);
}
return total / popsize;
}
/* Returns the median fitness of the population */
double genetic_median_fitness(Chromosome *population, int popsize) {
int median = popsize / 2;
return fitness_chromosome(population[median]);
}
/* This function takes a chromosome from the genetic algorithm and splits
it into groups. It returns an array to the formed groups. Remember to free */
Group* genetic_chromosome_to_groups(Chromosome chromo) {
int i, j, currentPerson;
/* personPerGroup is the minimum size of each group. leftoverPerons
is the amount of groups, which have an extra member */
int personPerGroup = chromo.personCount / chromo.groupCount;
int leftoverPersons = chromo.personCount % personPerGroup;
/* Allocate memory */
Group *groups = (Group*)malloc(chromo.groupCount * sizeof(Group));
currentPerson = 0;
for (i = 0; i < chromo.groupCount; i++) {
/* Setup some data about the group */
groups[i].groupNumber = i;
groups[i].fitnessValue = -1;
/* Add persons to group */
for (j = 0; j < personPerGroup; j++) {
groups[i].members[j] = chromo.persons[currentPerson];
currentPerson++;
}
/* Add another person if i < leftoverPersons.
set the memberCount variable in the group struct */
if (i < leftoverPersons) {
groups[i].members[j] = chromo.persons[currentPerson];
currentPerson++;
groups[i].memberCount = personPerGroup + 1;
} else {
groups[i].memberCount = personPerGroup;
}
}
return groups;
}
/* Compare function used to sort chromosomes. Will sort in descending order */
int genetic_q_compare(const void * i, const void * j) {
Chromosome *a = (Chromosome*)i;
Chromosome *b = (Chromosome*)j;
double fa = fitness_chromosome(*a);
double fb = fitness_chromosome(*b);
/* Return -1 if A is best, returns 1 if B is best */
if (fa > fb) return -1;
if (fb > fa) return 1;
return 0;
}
/* Returns the fitness of a chromosome */
double fitness_chromosome(Chromosome chromo) {
/* Split chromosome into groups, then calculate fitness */
Group *groups = genetic_chromosome_to_groups(chromo);
double fitness = fitness_groups(groups, chromo.groupCount, chromo.criteria, chromo.criteriaCount);
free(groups);
return fitness;
}
/* Returns the total fitness of all groups */
double fitness_groups(Group *groups, int groupCount, Criteria *criteria, int criteriaCount) {
double result = 0;
int i;
/* Calls the fitness_group function as many times as the number of
groups and summerize the fitness */
for (i = 0; i < groupCount; i++) {
result += fitness_group(groups + i, criteria, criteriaCount);
}
return result;
}
/* Returns the total fitness of a single group */
double fitness_group(Group *group, Criteria *criteria, int criteriaCount) {
double result = 0, average, criteriaMin, criteriaMax, t;
int i, j;
/* Goes through all criteria */
for (i = 0; i < criteriaCount; i++) {
/* Finds the average value of specific criteria */
average = average_criteria(group, i);
/* Finds min and max values of specific criteria in group */
for (j = 0; j < group->memberCount; j++) {
double a = group->members[j].criteria[i];
/* If min/max is not set yet, set them to a */
if (j == 0) {
criteriaMin = a;
criteriaMax = a;
} else {
/* Calls min/max function and finds min/max */
criteriaMin = _min(criteriaMin, a);
criteriaMax = _max(criteriaMax, a);
}
}
/* t is the average compared to min and max */
t = inverse_lerp(criteriaMin, criteriaMax, average);
/* Adds the fitness of the specific criteria in the single group to result */
result += fitness_of_criteria(t, criteria[i].weight, fitness_alpha(group->memberCount, criteria[i].minimum));
}
/* Save fitness in the group struct */
group->fitnessValue = result;
return result;
}
/* Returns fitness of specific criteria, if minimum isn't satisfied, return 0 */
double fitness_of_criteria(double t, double weight, double alpha) {
if (alpha <= t && t <= 1 - alpha) {
return (-4 * weight * pow(t, 2)) + (4 * weight * t) ;
} else {
return 0;
}
}
/* returns alpha from groupsize and miminum specified */
double fitness_alpha(int groupSize, double minimum) {
return _min((minimum / groupSize), 1 - (minimum / groupSize));
}
/* Finds average of one criteria */
double average_criteria(Group *g, int i) {
double result = 0;
int j;
/* Find sum of group's criteria */
for (j = 0; j < g->memberCount; j++) {
result += g->members[j].criteria[i];
}
return result / g->memberCount;
}
/* Selects two random parents from the upper half of population */
void genetic_selection(Chromosome *population, int popsize, Chromosome *par1, Chromosome *par2) {
int a = rand() % (popsize / 2);
int b = rand() % (popsize / 2);
*par1 = population[a];
*par2 = population[b];
}
/* Takes two parent chromosome and creates two children, which are stored at child pointers */
void genetic_crossover(Chromosome parent1, Chromosome parent2, Chromosome *child1, Chromosome *child2) {
int personCount = parent1.personCount;
/* The bitstring is used to determine which parent genes is taken from */
int *bitstring = (int*) calloc(personCount, sizeof(int));
int i, limit = personCount + 1;
/* Do cycle */
i = 0;
do {
int indexInPar2;
/* Mark index i */
bitstring[i] = 1;
/* Find index of element parent1.person[i] in parent2 */
for (indexInPar2 = 0; indexInPar2 < personCount; indexInPar2++) {
if (parent1.persons[i].personID == parent2.persons[indexInPar2].personID) {
/* Set index i to indexInPar2 */
i = indexInPar2;
break;
}
}
limit--;
} while (i != 0 && limit > 0);
assert(limit > 0);
/* Copy flagged genes from the other chromosome */
for (i = 0; i < personCount; i++) {
if (bitstring[i]) {
child1->persons[i] = parent1.persons[i];
child2->persons[i] = parent2.persons[i];
} else {
child1->persons[i] = parent2.persons[i];
child2->persons[i] = parent1.persons[i];
}
}
free(bitstring);
}
/* Takes a pointer to chromosome and slighty alter it */
void genetic_mutation(Chromosome *chromo, float mutationrate) {
/* Small chance of mutation */
float rn = (rand() % 1000) / 1000.;
if (rn <= mutationrate) {
Person temp;
/* Find two random indexes */
int i = 0, j = 0;
while (i == j && chromo->personCount > 0) {
i = rand() % chromo->personCount;
j = rand() % chromo->personCount;
}
/* Swaps two random elements */
temp = chromo->persons[i];
chromo->persons[i] = chromo->persons[j];
chromo->persons[j] = temp;
}
}
/* Returns a pointer to an array of initialized chromosomes. Remeber to call free */
Chromosome * genetic_get_empty_population(int popsize, DataSet data, int groupCount) {
int i;
/* Get memory for population */
Chromosome *population = genetic_get_memory_for_pop(popsize, data.personCount);
/* Init each population */
for (i = 0; i < popsize; i++) {
population[i].personCount = data.personCount;
population[i].criteria = data.allCriteria;
population[i].criteriaCount = data.criteriaCount;
population[i].groupCount = groupCount;
}
return population;
}
/* Returns a pointer to an array of new chromosomes. Remember to call free */
Chromosome * genetic_get_memory_for_pop(int popsize, int personCount) {
int i;
/* Create an array of chromosomes */
Chromosome *population = (Chromosome*)malloc(popsize * sizeof(Chromosome));
/* Create each chromosome's array of persons */
for (i = 0; i < popsize; i++) {
population[i].persons = (Person*)malloc(personCount * sizeof(Person));
}
return population;
}
/* Generates initial population by allocating memory and
setting chromosomes to random permutations */
Chromosome * genetic_generate_initial_population(int popsize, DataSet data, int groupCount) {
int i;
/* Allocate memory to the population */
Chromosome *population = genetic_get_empty_population(popsize, data, groupCount);
/* Generate chromosomes */
for (i = 0; i < popsize; i++) {
genetic_generate_chromosome(population + i, data);
}
return population;
}
/* Put members randomly into the chromosome array */
void genetic_generate_chromosome(Chromosome *chromosome, DataSet data) {
Person temp;
int i, n;
/* Fill chromosome with all persons systematically */
for (i = 0; i < data.personCount; i++) {
chromosome->persons[i] = data.allPersons[i];
}
/* Do Fisher Yates-algorithm for shuffling array */
for (i = 0; i < data.personCount; i++) {
n = rand() % (data.personCount - i) + i;
/* Swap index i and n */
temp = chromosome->persons[n];
chromosome->persons[n] = chromosome->persons[i];
chromosome->persons[i] = temp;
}
}
/* Copies the persons from one chromosome to another */
void genetic_copy_chromosome(Chromosome *to, Chromosome from, int personCount) {
int i;
for (i = 0; i < personCount; i++) {
to->persons[i] = from.persons[i];
}
}
/* Takes a population and cleans it up, freeing all used memory */
void genetic_kill_population(Chromosome *population, int popsize) {
int i;
/* Free all allocated memory for persons */
for (i = 0; i < popsize; i++) {
free(population[i].persons);
}
free(population);
}