source: liacs/ai/graaf/ga.cpp@ 291

Last change on this file since 291 was 2, checked in by Rick van der Zwet, 15 years ago

Initial import of data of old repository ('data') worth keeping (e.g. tracking
means of URL access statistics)

File size: 15.7 KB
RevLine 
[2]1/* File : ga.cpp
2 * Authors : Rick van der Zwet & Thomas Steenbergen
3 * S-number : 0433373 / 0117544
4 * Version : $Id: ga.cpp 612 2008-05-13 22:25:56Z rick $
5 * Licence : BSD
6 * Description : 4th Assignment AI 2008: Genetic Algoithm
7 */
8
9#include <iostream>
10#include <climits>
11#include <ctime>
12#include <cstdlib>
13#include <fstream>
14#include <math.h>
15#include <string>
16#include <sysexits.h>
17
18using namespace std;
19
20/*NOTE: Graph can only have this many nodes */
21#define MAX_NODES 50
22#define MAX_ARCHS 250
23
24/*NOTE: Maximum numbers of newly generated children */
25#define MAX_POP 20
26
27/*NOTE: The chance to which we mutate a given point */
28#define MUT_LEV 50
29
30/*NOTE: The maximum number of generations that the algorithm runs */
31#define DEFAULT_LOOPS 100000
32
33#define DEFAULT_FILENAME "input.txt"
34#define MAX_COORDINATES 1000
35
36
37struct arch {
38 int a, b;
39 double distance;
40
41 arch() {
42 a = -1;
43 b = -1;
44 distance = -1;
45 }
46};
47
48/* Coordinate of point in graph */
49struct point {
50 int x, y; /* X,Y coordinates */
51
52 point(){
53 x = -1;
54 y = -1;
55 }
56};
57
58/* Comparision between 2 points */
59bool operator==(point &a, point &b) {
60 if ( (a.x == b.x) && (a.y == b.y))
61 return true;
62 else
63 return false;
64}
65
66struct graph {
67 point nodes[MAX_NODES]; /* Location of nodes */
68 int fitness; /* Overall fitness graph */
69 int fitnessDistance;
70 int fitnessIntersection;
71
72 graph() {
73 fitness = -1;
74 fitnessDistance = -1;
75 fitnessIntersection = -1;
76 }
77};
78
79/*
80 * BEGIN Global variables
81 */
82
83arch archs[MAX_ARCHS]; /* arch listing in graph */
84int num_archs = -1; /* Number of archs in graph */
85int num_nodes = -1; /* Number of nodes in graph */
86int max_cord = -1; /* Domain e.g. maximum coord of X, Y*/
87int lon_con = -1; /* Longest connection of two points */
88int distances [MAX_NODES][MAX_NODES]; /* Distances between node i and j */
89graph population[MAX_POP]; /* Graph list */
90
91/*
92 * END Global variables
93 */
94
95
96
97/* Calculates the distance between two points
98 * Distance (A,B) = d((x1,y1),(x2,y2))=SQRT((x1-x2)^2+(y1-y2)^2)
99 */
100double calcDistance(point A, point B) {
101 double dist = 0;
102 dist = sqrt(pow((A.x - B.x),2) + pow((A.y - B.y),2));
103 return dist;
104}
105
106/* How well is the scaling of the branches ofthis graph weel
107 * versus the input graph. The more it deviates of the orginal the higher
108 * the fitness number.
109 */
110int fitnessDistance(graph& A) {
111 int i,j;
112 int org_dist = 0; // distance between 2 points in input graph
113 double new_dist = 0; // distance between 2 points in population graph
114 double diff_dist = 0; // absolute difference
115 int tmp_fitness = 0;
116
117 for (i=0; i< num_nodes; i++) {
118 for (j=i+1; j< num_nodes; j++) {
119 org_dist = distances[i][j];
120 if (org_dist != 0){
121 new_dist = calcDistance(A.nodes[i],A.nodes[j]);
122 diff_dist = fabs(new_dist - org_dist);
123 tmp_fitness += diff_dist;
124 }
125 }
126 }
127
128 return(tmp_fitness);
129}
130
131/* Output point itself */
132void printPoint(point &A) {
133 cerr << A.x << "," << A.y;
134}
135
136/* Calculates whether the line A-B crosses with line C-D and wether in
137 * domain if so a it returns the point of intersection else return point
138 * [-1,-1] All explained in:
139 * http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2
140 * http://en.wikipedia.org/wiki/Line-line_intersection
141 */
142bool calcIntersection(point A, point B, point C, point D, point& tmp) {
143 double K, L, M;
144 double S, T, R;
145 double det;
146 bool result;
147 double distance_a_b;
148
149 // rewrite line A-B into formula form: Kx + Ly = M
150 K = B.y - A.y;
151 L = A.x - B.x;
152 M = K * A.x + L * A.y;
153
154 // rewrite line C-D into formula form: Sx + Ty = R
155 S = D.y - C.y;
156 T = C.x - D.x;
157 R = S * C.x + T * C.y;
158
159 // Now we calculate the intersection between the lines
160 det = K*T - S*L;
161
162 if(det == 0){
163 /* Lines A-B & C-D are parallel, checking wether they are on top of
164 * each other
165 */
166 tmp.x = -1;
167 tmp.y = -1;
168 result = false;
169
170 distance_a_b = calcDistance(A,B);
171 if ((calcDistance(A,C) + calcDistance(B,C) == distance_a_b)) {
172 tmp.x = C.x;
173 tmp.y = C.y;
174 result = false;
175 } else if ((calcDistance(A,D) + calcDistance(B,D) == distance_a_b)) {
176 tmp.x = D.x;
177 tmp.y = D.y;
178 result = false;
179 }
180
181 } else {
182 tmp.x = (T*M - L*R)/det;
183 tmp.y = (K*R - S*M)/det;
184 result = true;
185
186 /* Verify intersection in domain */
187 if (tmp.x < 0 || tmp.x >= max_cord || tmp.y < 0 || tmp.y >= max_cord) {
188 result = false;
189 }
190
191 /* Verify intersection not a actual end point */
192 if ((tmp == A || tmp == B) && (tmp == C || tmp == D)) {
193 result = false;
194 }
195 }
196
197 return result;
198}
199
200bool calcIntersection(point A, point B, point C, point D) {
201 point tmp;
202 return calcIntersection(A, B, C, D, tmp);
203}
204
205/*
206 * The number of intersections a graph. How more intersections the higher
207 * the fitness number.
208 */
209int fitnessIntersection(graph& A) {
210 int i,j;
211 int tmp_fitness = 0;
212
213 for (i = 0; i < num_archs; i++) {
214 for (j = i + 1; j < num_archs; j++) {
215 if ( calcIntersection(A.nodes[archs[i].a],
216 A.nodes[archs[i].b],
217 A.nodes[archs[j].a],
218 A.nodes[archs[j].b])) {
219 tmp_fitness++;
220 }
221 }
222 }
223 return(tmp_fitness);
224}
225
226
227/* Calculates the fitness of every graph in the population */
228void calcFitness(graph& A) {
229 A.fitnessIntersection = fitnessIntersection(A);
230 A.fitnessDistance = fitnessDistance(A);
231 A.fitness = A.fitnessDistance + A.fitnessIntersection;
232}
233
234void crossover(graph& A, graph& B){
235 /* XXX: Find clever way to combine the different graphs to be able
236 * to make new ones. Three to expiriment with:
237 * - uniform crossover
238 * - single-point crossover
239 * - partially mapped crossover
240 * All explained in: http://www.liacs.nl/~kosters/AI/genetisch.pdf
241 */
242
243}
244
245// Combine two graphs using single point crossover
246// A single random point is chosen in a graph's node
247// array dividing it into two halves e.g. the head and the tail.
248// Then heads are swapped between parents A & B
249void crossSingle(graph& A, graph& B){
250 point tmp;
251 unsigned int cut;
252 unsigned int i;
253
254 cut = rand() % num_nodes;
255 for (i=0; i< cut;i++) {
256 tmp = A.nodes[i];
257 A.nodes[i] = B.nodes[i];
258 B.nodes[i] = tmp;
259 }
260}
261
262// Combine two graphs using uniform crossover
263// The points are swapped with a fixed probability of 0.5.
264void crossUniform(graph& A, graph& B){
265 point tmp;
266 int i, rnd;
267
268 for (i=0; i< num_nodes;i++) {
269 rnd = rand()% 2;
270 if (rnd == 1){
271 tmp.x = A.nodes[i].x;
272 A.nodes[i].x = B.nodes[i].x;
273 B.nodes[i].x = tmp.x;
274 }
275 rnd = rand()% 2;
276 if (rnd == 1){
277 tmp.y = A.nodes[i].y;
278 A.nodes[i].y = B.nodes[i].y;
279 B.nodes[i].y = tmp.y;
280 }
281 }
282}
283
284
285// Copies the contents of graph A to graph B
286void copyGraph(graph & A, graph& B){
287 int i;
288
289 for (i=0; i< num_nodes;i++) {
290 B.nodes[i] = A.nodes[i];
291 }
292 B.fitness = A.fitness;
293 B.fitnessIntersection = A.fitnessIntersection;
294 B.fitnessDistance = A.fitnessDistance;
295}
296
297/* Mutate random point in a graph and change it to random value
298 * within the domain of points
299 */
300void mutateGraph (int mutationLevel, graph& A){
301 int i,x,y;
302
303 if ((rand() % 100) > mutationLevel) {
304 return;
305 }
306
307 i = rand() % num_nodes;
308 x = (rand()% max_cord)+1;
309 y = (rand()% max_cord)+1;
310
311 A.nodes[i].x = x;
312 A.nodes[i].y = y;
313}
314
315/* To do selection we use roulettewheel selection, only we
316 * invert adjust the regular algorithm so it prefers
317 * the lowest fitness numbers e.g. the biggest slice of
318 * piece is now the least attractive.
319 */
320int selectGraph() {
321 int i;
322 int choice = -1;
323 int combined_fitness;
324 int fitness_reverse[MAX_POP];
325 int max_fitness = INT_MIN;
326 int min_fitness = INT_MAX;
327 int total_fitness = 0;
328 int wheelnumber;
329
330 /* Find minimum/maximum */
331 min_fitness = population[0].fitness;
332 max_fitness = population[MAX_POP - 1].fitness;
333
334 /* Set balanced fitness */
335 combined_fitness = min_fitness + max_fitness;
336 for(i=0; i< MAX_POP; i++) {
337 fitness_reverse[i] = combined_fitness - population[i].fitness;
338 total_fitness += fitness_reverse[i];
339 }
340
341 /* Get random number of wheel */
342 wheelnumber = rand() % total_fitness;
343
344 /* Find matching graph */
345 total_fitness = 0;
346 for(i=0; i< MAX_POP; i++) {
347 total_fitness += fitness_reverse[i];
348 if (total_fitness > wheelnumber) {
349 choice = i;
350 break;
351 }
352 }
353
354 return (choice);
355}
356
357/* Set the values of a given graph to random numbers
358 * In other word those graphs who aint fit enough
359 * for the next round will be discarded.
360 */
361void setRandGraph(graph& A) {
362 int i;
363 for (i=0; i< num_nodes;i++) {
364 A.nodes[i].x = (rand()%max_cord)+1;
365 A.nodes[i].y = (rand()%max_cord)+1;
366 }
367
368 calcFitness(A);
369}
370
371/* Create a graph and set nodes to certain location */
372graph initGraph() {
373 graph A;
374 setRandGraph(A);
375 return A;
376}
377
378// Generates a population of MAX_POP graphs with random coordinates
379void initPopulation () {
380 int i;
381
382 for (i=0; i< MAX_POP;i++) {
383 population[i] = initGraph();
384 }
385}
386// Using bubblesort we sort the graphs in the population on fitness
387void sortPopulation () {
388 graph tmp;
389 int i, j;
390 for (j = 1; j < MAX_POP; j++ ) {
391 for (i = 0; i < MAX_POP-j; i++ ) {
392 if (population[i].fitness > population[i+1].fitness) {
393 tmp = population[i];
394 population[i] = population[i+1];
395 population[i+1] = tmp;
396 }
397 }
398 }
399}
400
401/* Opens input file with adjacency-matrix which contains data about the
402 * number of nodes (N) on the first line and the values of the
403 * connections between those nodes in a N x N matrix in the following
404 * lines
405 */
406void openFile(char * inputFile) {
407 ifstream input;
408 int i, j;
409 input.open(inputFile, ios::in);
410
411 if (input) {
412 cerr << "Opening "<< inputFile << "..." << endl;
413 input >> num_nodes;
414 if (num_nodes <= 0) {
415 input.close();
416 cerr << "Error: Invalid data format!" << endl;
417 exit(EX_DATAERR);
418 } else if (num_nodes < MAX_NODES) {
419 for (i=0; i< num_nodes; i++) {
420 for (j=0; j< num_nodes; j++) {
421 input >> distances[i][j];
422 if (input.eof()) {
423 cerr << "Error: Invalid data format!" <<endl;
424 exit(EX_DATAERR);
425 }
426 if (distances[i][j] > lon_con){
427 lon_con = distances[i][j];
428 }
429 }
430 }
431 } else {
432 input.close();
433 cerr << "Error: Number of nodes in "<< inputFile;
434 cerr << " exceeds maximum number of nodes" << endl;
435 exit(EX_DATAERR);
436 }
437 } else {
438 input.close();
439 cerr << "Error: Couldn't open file " << inputFile << endl;
440 exit(EX_NOINPUT);
441 }
442
443 /* Transform 2d structure to 1d archs to allow easy computations */
444 num_archs = 0;
445 for (i = 0; i < num_nodes; i++) {
446 for (j = i+1; j < num_nodes; j++) {
447 if (distances[i][j] > 0) {
448 archs[num_archs].a = i;
449 archs[num_archs].b = j;
450 archs[num_archs].distance = distances[i][j];
451 num_archs++;
452 }
453 }
454 }
455}
456
457// Prints the adjacency-matrix of openFile() to the screen
458void printDistances() {
459 int i, j;
460
461 cout << " " << endl;
462 for (i=0; i< num_nodes; i++) {
463 for (j=0; j< num_nodes; j++) {
464 cout << distances[i][j]<< " ";
465 }
466 cout << endl;
467 }
468 cout << " " << endl;
469}
470
471/* Prints the coordinates of every point in a graph to the screen in
472 * graphviz compatible output
473 * cat <<EOF | neato -Tpng -oga.png && open ga.png
474 */
475void printGraph(graph& A) {
476 int i;
477
478 cout << "graph G { node [shape=circle,"
479 << "fontname=\"Lucida Console\",margin=0,0];" << endl;
480 for (i=0; i< num_nodes; i++) {
481 cout << "C" << i << "[pos=\"" << A.nodes[i].x * 28 << "," <<
482 A.nodes[i].y * 28 << "!\", label=\"C" << i << "\"];" << endl;
483 }
484 for (i=0; i < num_archs; i++) {
485 cout << "C" << archs[i].a << " -- C" << archs[i].b << ";" << endl;
486 }
487 cout << "}" << endl;
488}
489
490
491// Prints every graph stored in array population
492void printPopulation () {
493 int i;
494 cerr << " " << endl;
495 for (i=0; i< MAX_POP; i++) {
496 cerr << i << " => ";
497 printGraph(population[i]);
498 }
499 cerr << " " << endl;
500}
501
502
503/* Implementation of Steady State Evolution Algorithm based on p.119 AI
504 * Book */
505int main(int argc, char * argv[]) {
506 int i,j;
507 int org_dist, new_dist;
508 int select_one, select_two;
509 int loopCounter;
510 unsigned int randomSeed;
511
512 graph min;
513 graph child_one, child_two;
514
515 // use random seed to create x,y coordinates of a point
516 randomSeed = (unsigned)time(0);
517 randomSeed = 0;
518 srand(randomSeed);
519 /* Debug static seed */
520 // srand(0);
521
522 // Open the file that contains data about
523 // the number odf branches and which branches are
524 // connected with each other
525 if (argc >= 2) {
526 openFile(argv[1]);
527 } else {
528 cerr << "Usage: " << argv[0] << " <filename> <loopCount>" << endl;
529 exit(EX_USAGE);
530 }
531
532 if (argc == 3) {
533 loopCounter = atoi(argv[2]);
534 } else {
535 loopCounter = DEFAULT_LOOPS;
536 }
537
538 /* To optimize the speed of the genetic algoritm we limit the domain
539 * of the points */
540 if ((lon_con * num_nodes) < MAX_COORDINATES){
541 max_cord = lon_con * num_nodes;
542 } else {
543 max_cord = MAX_COORDINATES;
544 }
545 cerr << "Domain of points is set to "
546 << max_cord << " x " << max_cord << endl;
547
548 // Minimum graph to store best found solution
549 min.fitness = INT_MAX;
550
551 /* Populate population, with random values */
552 initPopulation();
553
554 for (i=0; i< loopCounter; i++) {
555 // Sort the population of graphs so that graph
556 // with smallest fitness is placed in population[0]
557 sortPopulation();
558
559 // Store the lowest found fitness if it is better
560 // then the fitness we already had stored
561 if (min.fitness > population[0].fitness){
562 copyGraph(population[0],min);
563 }
564
565 // Stop if optimal is found e.g fitness equals zero
566 if(population[0].fitness == 0){
567 break;
568 }
569
570 // Selection reproducing parents via roulette wheel
571 // fittest parents get selected
572 select_one = selectGraph();
573 do {
574 select_two = selectGraph();
575 }while (select_one == select_two);
576
577 // set points in children with crossover result of parents
578 copyGraph(population[select_one], child_one);
579 copyGraph(population[select_two], child_two);
580
581 // Crossover the selected parents
582 crossUniform(child_one, child_two);
583
584 // mutate childs with small random probability usually 50%
585 mutateGraph(99, child_one);
586 mutateGraph(99, child_two);
587
588 // Calculate fitness of both children
589 calcFitness(child_one);
590 calcFitness(child_two);
591
592 // Least fittest graphs in population get replaced
593 copyGraph(child_one,population[MAX_POP -2]);
594 copyGraph(child_two,population[MAX_POP -1]);
595 }
596
597 cerr << "Best found coordinates after "<< i <<
598 " epochs for given input graph: " << endl;
599 printGraph(min);
600
601 if(min.fitness != 0){
602 for (i=0; i< num_nodes; i++) {
603 for (j=i+1; j< num_nodes; j++) {
604 org_dist = distances[i][j];
605 if (org_dist != 0){
606 new_dist = calcDistance(min.nodes[i],min.nodes[j]);
607 cerr << "Distance between point C"<< i << " - C" << j << " = ";
608 cerr << calcDistance(min.nodes[i],min.nodes[j]) << " ";
609 if (new_dist != org_dist){
610 cerr << "SHOULD BE " << org_dist << endl;
611 } else {
612 cerr << "CORRECT" << endl;
613 }
614 }
615 }
616 }
617 }
618 cerr << "Fitness Intersection : " << min.fitnessIntersection << endl;
619 cerr << "Fitness Distance : " << min.fitnessDistance << endl;
620 cerr << "Fitness Overall : " << min.fitness << endl;
621 cerr << "Random seed : " << randomSeed << endl;
622 return(EX_OK);
623}
Note: See TracBrowser for help on using the repository browser.