Pac-Man Evolution
Loading...
Searching...
No Matches
GeneticAlgorithm.cpp
1/*----------------------------------------------------------------------
2Pac-Man Evolution - Roberto Prieto
3Copyright (C) 2018-2024 MegaStorm Systems
4contact@megastormsystems.com - http://www.megastormsystems.com
5
6This software is provided 'as-is', without any express or implied
7warranty. In no event will the authors be held liable for any damages
8arising from the use of this software.
9
10Permission is granted to anyone to use this software for any purpose,
11including commercial applications, and to alter it and redistribute it
12freely, subject to the following restrictions:
13
141. The origin of this software must not be misrepresented; you must not
15claim that you wrote the original software. If you use this software
16in a product, an acknowledgment in the product documentation would be
17appreciated but is not required.
182. Altered source versions must be plainly marked as such, and must not be
19misrepresented as being the original software.
203. This notice may not be removed or altered from any source distribution.
21
22------------------------------------------------------------------------
23
24Genetic Algorithm class
25
26Based on the amazing docs created by Mat Buckland (http://www.ai-junkie.com/ga/intro/gat1.html)
27
28------------------------------------------------------------------------ */
29
30#include "GeneticAlgorithm.h"
31#include "ArtificialNeuralNet.h"
32#include <vector>
33#include <algorithm>
34#include <iostream>
35#include <fstream>
36
37// Constructor
38GeneticAlgorithm::GeneticAlgorithm(Sint32 popsize, double MutRat, double CrossRat, double MaxPerturbation, Sint32 NumElite, Sint32 NumCopiesElite, Sint32 numweights)
39{
40 iPopulation = popsize;
41 dMutationRate = MutRat;
42 dCrossoverRate = CrossRat;
43 iChromoLength = numweights;
44 dTotalFitness = 0;
45 iGeneration = 0;
46 iFittestGenome = 0;
47 dFitnessBest = 0;
48 dWorstFitness = 99999999;
49 dFitnessAverage = 0;
50 dMaxPerturbation = MaxPerturbation;
51 iNumElite = NumElite;
52 iNumCopiesElite = NumCopiesElite;
53 iNumMutation = iNumCrossOver = 0;
54}
55
56// Initialize the chromosomes for each creature of this population
57void GeneticAlgorithm::init(Sint32 iPopPosition,vector<double> chromosomes)
58{
59 Sint32 i;
60
61 // Security checks
62 if(iPopPosition > iPopulation) return;
63 if(chromosomes.size() != iChromoLength) return;
64
65 // Create a new genome
66 vPopulation.push_back(Genome());
67
68 // Assign chromosomes
69 for(i = 0; i<iChromoLength; ++i)
70 {
71 vPopulation[iPopPosition].vWeights.push_back(chromosomes[i]);
72 }
73}
74
75// Mutates a chromosome by perturbing its weights
76void GeneticAlgorithm::mutate(vector<double>& chromo)
77{
78 Sint32 i;
79 Tool &mTool = Main::Instance().ITool();
80
81 for(i = 0; i < (Sint32)chromo.size(); ++i)
82 {
83 // Do we perturb this weight?
84 if (mTool.randRealWELL() < dMutationRate)
85 {
86 // Add or subtract a small value to the weight
87 chromo[i] += generateWeights() * dMaxPerturbation;
88 ++iNumMutation;
89 //printf("Genetic Mutation\n");
90 }
91 }
92}
93
94// Return a chromosome based on Roulette wheel sampling
95// (having the higher fitness does not warranty to be the one chosen..just give it more chances!)
96Genome GeneticAlgorithm::getChromoRoulette()
97{
98 Genome TheChosenOne;
99 double FitnessSoFar = 0;
100 Sint32 i;
101
102 // Generate a random number between 0 & total fitness count
103 double Slice = (double)(Main::Instance().ITool().randRealWELL() * dTotalFitness);
104
105 for(i = 0; i < iPopulation; ++i)
106 {
107 FitnessSoFar += vPopulation[i].dFitness;
108 // If the fitness so far > random number return the chromo at this point
109 if(FitnessSoFar >= Slice)
110 {
111 TheChosenOne = vPopulation[i];
112 break;
113 }
114 }
115 return TheChosenOne;
116}
117
118// Given parents and storage for the offspring this method performs crossover according to the GAs crossover rate
119void GeneticAlgorithm::crossover(const vector<double>& mum, const vector<double>& dad, vector<double>& baby1, vector<double>& baby2)
120{
121 Sint32 cp,i;
122 Tool &mTool = Main::Instance().ITool();
123
124 // If the parents are the same, they will be also the babies
125 if((mTool.randRealWELL() > dCrossoverRate) || (mum == dad))
126 {
127 baby1 = mum;
128 baby2 = dad;
129 return;
130 }
131 //printf("Genetic CrossOver\n");
132 ++iNumCrossOver;
133
134 // Determine a crossover point
135 cp = mTool.randWELL() % (iChromoLength);
136
137 // Create the offspring
138 for(i = 0; i < cp; ++i)
139 {
140 baby1.push_back(mum[i]);
141 baby2.push_back(dad[i]);
142 }
143
144 for(i = cp; i < (Sint32)mum.size(); ++i)
145 {
146 baby1.push_back(dad[i]);
147 baby2.push_back(mum[i]);
148 }
149
150 return;
151}
152
153//-----------------------------------Epoch()-----------------------------
154// takes a population of chromosones and runs the algorithm through one cycle.
155// Returns a new population of chromosones.
156//-----------------------------------------------------------------------
157vector<Genome> GeneticAlgorithm::epoch(vector<Genome>& old_pop)
158{
159 vPopulation = old_pop;
160 reset();
161
162 // Sort the population (for scaling and elitism)
163 sort(vPopulation.begin(), vPopulation.end());
164
165 // Calculate best, worst, average and total fitness
166 calculateStats();
167
168 // Create a temporary vector to store new chromosones
169 vector <Genome> vecNewPop;
170
171 // Now to add a little elitism we shall add in some copies of the fittest genomes.
172 if((iNumCopiesElite * iNumElite) < iPopulation)
173 {
174 grabNBest(iNumElite, iNumCopiesElite, vecNewPop);
175 }
176
177 // Repeat until a new population is generated
178 while((Sint32)vecNewPop.size() < iPopulation)
179 {
180 vector<double> baby1, baby2;
181
182 // Get the mum and dad
183 Genome mum = getChromoRoulette();
184 Genome dad = getChromoRoulette();
185
186 // Crossover
187 crossover(mum.vWeights, dad.vWeights, baby1, baby2);
188 // Mutate
189 mutate(baby1);
190 mutate(baby2);
191
192 // Support odd populations
193 vecNewPop.push_back(Genome(baby1, 0));
194 if((Sint32)vecNewPop.size() < iPopulation) vecNewPop.push_back(Genome(baby2, 0));
195 }
196
197 // Return the new population
198 vPopulation = vecNewPop;
199 return vPopulation;
200}
201
202vector<Genome> GeneticAlgorithm::getChromos() const
203{
204 return vPopulation;
205}
206
207double GeneticAlgorithm::fitnessAverage() const
208{
209 return dFitnessAverage;
210}
211
212double GeneticAlgorithm::fitnessBest() const
213{
214 return dFitnessBest;
215}
216
217Sint32 GeneticAlgorithm::getMutation() const
218{
219 return iNumMutation;
220}
221
222Sint32 GeneticAlgorithm::getCrossOver() const
223{
224 return iNumCrossOver;
225}
226
227// This works like an advanced form of elitism by inserting NumCopies
228// copies of the NBest most fittest genomes into a population vector
229void GeneticAlgorithm::grabNBest(Sint32 NBest, const Sint32 NumCopies, vector<Genome>& Pop)
230{
231 Sint32 i;
232
233 // Add the required amount of copies of the n most fittest to the supplied vector
234 while(NBest--)
235 {
236 for(i = 0; i < NumCopies; ++i)
237 {
238 Pop.push_back(vPopulation[(iPopulation - 1) - NBest]);
239 }
240 }
241}
242
243// Calculates some stats of the current generation
244void GeneticAlgorithm::calculateStats()
245{
246 Sint32 i;
247 double HighestSoFar = 0;
248 double LowestSoFar = 9999999;
249 dTotalFitness = 0;
250
251 for(i = 0; i < iPopulation; ++i)
252 {
253 if(vPopulation[i].dFitness > HighestSoFar)
254 {
255 HighestSoFar = vPopulation[i].dFitness;
256 iFittestGenome = i;
257 dFitnessBest = HighestSoFar;
258 }
259
260 if(vPopulation[i].dFitness < LowestSoFar)
261 {
262 LowestSoFar = vPopulation[i].dFitness;
263 dWorstFitness = LowestSoFar;
264 }
265 dTotalFitness += vPopulation[i].dFitness;
266 }
267
268 dFitnessAverage = dTotalFitness / iPopulation;
269}
270
271// Resets all the relevant variables ready for a new generation
272void GeneticAlgorithm::reset()
273{
274 dTotalFitness = 0;
275 dFitnessBest = 0;
276 dWorstFitness = 9999999;
277 dFitnessAverage = 0;
278 iNumMutation = iNumCrossOver = 0;
279}
280