-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathenvironment.cpp
More file actions
381 lines (336 loc) · 15.9 KB
/
Copy pathenvironment.cpp
File metadata and controls
381 lines (336 loc) · 15.9 KB
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
#include "environment.h"
#include <algorithm>
#include <QDebug>
//To do:
//Perturbations - make this an environment type - and test 17
//Failing tests 0,1,2,5,17 / test mean window user set value sticks
//check when done that all attributes are correctly copied in equals
// We call this constructor when we want to create a new environment from scratch
Environment::Environment(const simulationVariables &simSettingsCon)
{
mutationRate = simSettingsCon.environmentMutationRate;
environmentType = simSettingsCon.environmentType;
environmentMutationMaxJump = simSettingsCon.environmentMutationMaxJump;
environmentMutationJump = simSettingsCon.environmentMutationJump;
//Set up vectors that will serve as masks for this environment
for (int j = 0; j < simSettingsCon.maskNumber; j++)
{
masks.append(QVector <bool>());
for (int i = 0; i < simSettingsCon.fitnessSize; i++)
{
//This will generate a random integer between 0 (inclusive) and 2 (exclusive) - so either 0 or 1. Break it out for clarity
int random = QRandomGenerator::global()->bounded(0, 2);
if (random == 0) masks[j].append(bool(false));
else if (random == 1) masks[j].append(bool(true));
else error = true; //This should never happen
}
}
}
//We call this when we want to initialise to a known state to test the fitness algorithm
Environment::Environment(const int &maskNumber, const int &maskLength, const bool initialiseState)
{
//Set up vectors that will serve as masks for this environment
for (int j = 0; j < maskNumber; j++) masks.append(QVector <bool>(maskLength, initialiseState));
}
//We call this one when we want to create an environment from another - either shuffled but with matching peaks, or just copying the first
Environment::Environment(const Environment &constructorEnvironment, bool matchingPeaksCon)
{
if (matchingPeaksCon)
{
mutationRate = constructorEnvironment.mutationRate;
environmentType = ENVIRONMENT_TYPE_MATCHING_PEAKS;
//If we need to make sure fitness peaks are the same height, in TREvoSim, we need to initialise with the same number of 1s in each site
//An easy way to do this is to shuffle the columns/sites between the incoming environment and the one we are creating
//First let's create an iota vector and shuffle it, using this to change the sites (i.e. bits)
QVector<int> sites(constructorEnvironment.masks[0].length());
std::iota(sites.begin(), sites.end(), 0);
std::shuffle(sites.begin(), sites.end(), *QRandomGenerator::global());
//Use this to write the new environment based on the incoming one
for (int j = 0; j < constructorEnvironment.masks.length(); j++)
{
masks.append(QVector <bool>());
for (int i = 0; i < sites.length(); i++)
{
//Here we use the ith entry in the shuffled sites list to define the site we copy over from the incoming mask
if (constructorEnvironment.masks[j][sites[i]]) masks[j].append(bool(true));
else masks[j].append(bool(false));
}
}
}
else
{
mutationRate = constructorEnvironment.mutationRate;
environmentType = constructorEnvironment.environmentType;
for (int j = 0; j < constructorEnvironment.masks.length(); j++)
{
masks.append(QVector <bool>());
for (int i = 0; i < constructorEnvironment.masks[j].length(); i++)
{
//Here we use the ith entry in the shuffled sites list to define the site we copy over from the incoming mask
if (constructorEnvironment.masks[j][i]) masks[j].append(bool(true));
else masks[j].append(bool(false));
}
}
}
}
void Environment::operator = (const Environment &E)
{
// Copy all attributes
masks = E.masks;
mutationRate = E.mutationRate;
environmentType = E.environmentType;
environmentalPerturbationCopyRate = E.environmentalPerturbationCopyRate;
perturbationStart = E.perturbationStart;
perturbationEnd = E.perturbationEnd;
environmentalPerturbationMasksCopy = E.environmentalPerturbationMasksCopy;
environmentalPerturbationOverwriting = E.environmentalPerturbationOverwriting;
}
bool Environment::operator == (const Environment &E)
{
if (masks != E.masks) return false;
if (mutationRate != E.mutationRate) return false;
if (environmentType != E.environmentType) return false;
return true;
}
bool Environment::operator != (const Environment &E)
{
if (masks == E.masks) return false;
if (mutationRate == E.mutationRate) return false;
if (environmentType == E.environmentType) return false;
return true;
}
bool Environment::mutate()
{
if (environmentType == ENVIRONMENT_TYPE_RANDOM)
{
for (int j = 0; j < masks.length(); j++)
for (int i = 0; i < masks[j].length(); i++)
{
//This will generate a random integer between 0 (inclusive) and 2 (exclusive) - so either 0 or 1. Break it out for clarity
int random = QRandomGenerator::global()->bounded(0, 2);
if (random == 0) masks[j][i] = false;
else if (random == 1) masks[j][i] = true;
else return false; //This should never happen
}
}
//Treat these together as they require a lot of the same setup - only the mutation process really differs
else if (environmentType == ENVIRONMENT_TYPE_MATCHING_PEAKS || environmentType == ENVIRONMENT_TYPE_CONSTANT || environmentType == ENVIRONMENT_TYPE_PREDICTABLE_WALK
|| environmentType == ENVIRONMENT_TYPE_UNPREDICTABLE_WALK)
{
if (environmentType == ENVIRONMENT_TYPE_PREDICTABLE_WALK || environmentType == ENVIRONMENT_TYPE_UNPREDICTABLE_WALK)
{
if (mutationRate > 50.)
{
mutationRate = 50.;
}
if (mutationRate < 0.)
{
mutationRate = 0.;
//In this case, we don't need to do anything further - no mutations required
return true;
}
if (environmentType == ENVIRONMENT_TYPE_PREDICTABLE_WALK)
{
if (QRandomGenerator::global()->bounded(1) == 1) mutationRate += environmentMutationJump;
else mutationRate -= environmentMutationJump;
}
if (environmentType == ENVIRONMENT_TYPE_UNPREDICTABLE_WALK)
{
double jump = (QRandomGenerator::global()->bounded(environmentMutationMaxJump));
if (QRandomGenerator::global()->bounded(1) == 1) mutationRate += jump;
else mutationRate -= jump;
}
}
//Set our mutation rate
double localMutationRate = mutationRate;
//If we are matching peaks, we want the mutation rate to be halved because we will need to switch a zero to a one and one to a zero or vice versa.
//So every mutation is two bit changes
if (environmentType == ENVIRONMENT_TYPE_MATCHING_PEAKS) localMutationRate /= 2;
//Define the mask length as we will need to use it multiple times
int maskLength = masks[0].length();
int maskNumber = masks.length();
//As per docs, mutations are set per 100 bits in the genome - calculate for this environment, first total bit number
int totalBitsPerEnvironment = masks[0].length() * masks.length();
//Then calculate mutation # for this environment
double numberEnvironmentMutationsDouble = (static_cast<double>(totalBitsPerEnvironment) / 100.) * localMutationRate;
//This will be used to store the integral part of the above - it needs to be a double as this is what is passed to the modf function
double numberEnvironmentMutationsIntegral = numberEnvironmentMutationsDouble;
//Next sort out the probabilities of extra mutation given remainder
double numberEnvironmentMutationsFractional = modf(numberEnvironmentMutationsDouble, &numberEnvironmentMutationsIntegral);
int numberEnvironmentMutationsInteger = (static_cast<int>(numberEnvironmentMutationsIntegral));
if (QRandomGenerator::global()->generateDouble() < numberEnvironmentMutationsFractional) numberEnvironmentMutationsInteger++;
//note that due to saturation / multiple hits on one site, the number of recorded mutations in e.g. our tests may sneak in under the expected value
if (environmentType == ENVIRONMENT_TYPE_MATCHING_PEAKS)
{
//If we are matching peaks, it's best to think of bit positions as columns. We want to shuffle around columns in the 'x' direction to achieve on swapped bit (= two bit changes, hence the half rate above - it is impossible to do this without swapping two bits)
//The way this is organised, we want to do this within each environment
//reminder: masks[environment #][mask #][bit #]
//Create lists of columns separated by one bit to do swap
QList <int> pairOne;
QList <int> pairTwo;
//Used to do the exhaustively, but this was massive overkill for most settings, and made the function slooooow
//Now use heuristic approach with an appopriate escape and error message
int count = 0;
do
{
int firstBit = QRandomGenerator::global()->bounded(maskLength);
int secondBit = QRandomGenerator::global()->bounded(maskLength);
if (firstBit == secondBit) continue;
int bitDifference = 0;
for (int n = 0; n < maskNumber; n++)
if (masks[n][firstBit] != masks[n][secondBit]) bitDifference++;
if (bitDifference == 1)
if (!pairOne.contains(firstBit) && !pairOne.contains(secondBit) && !pairTwo.contains(firstBit) && !pairTwo.contains(secondBit))
{
pairOne.append(firstBit);
pairTwo.append(secondBit);
}
count++;
}
while (pairOne.length() < numberEnvironmentMutationsInteger && count < 10000);
//Add warning if there are not enough columns to swap: with any decent size genome, I don't expect this to happen all that much
if (pairOne.length() < numberEnvironmentMutationsInteger) return false;
//Otherwisse apply the mutations
else for (int x = 0; x < numberEnvironmentMutationsInteger; x++)
{
//Swap one pair of columns
int swap1 = pairOne[x];
int swap2 = pairTwo[x];
for (int m = 0; m < maskNumber; m++)
{
bool storeBit = masks[m][swap1];
masks[m][swap1] = masks[m][swap2];
masks[m][swap2] = storeBit;
}
}
}
//Or if we don't have matching peaks, we can just do the mutations
else
{
for (int x = 0; x < numberEnvironmentMutationsInteger; x++)
{
//Select random mask and random bit
int mutationPosition = QRandomGenerator::global()->bounded(maskLength);
int mutationMask = QRandomGenerator::global()->bounded(maskNumber);
//qDebug() << "m" << mutationPosition << mutationMask;
masks[mutationMask ][mutationPosition] = !masks[mutationMask ][mutationPosition];
}
}
}
else return false;
return true;
}
QString Environment::printMasks()
{
QString maskText;
QTextStream out(&maskText);
for (int maskNumber = 0; maskNumber < masks.length(); maskNumber++)
{
out << "Mask number " << maskNumber << " :\t";
for (auto b : masks[maskNumber]) b ? out << 1 : out << 0 ;
out << "\n";
}
return maskText;
}
//Count bits for fitness algorithm
quint32 Environment::bitCount(Organism const *o) const
{
quint32 counts = 0;
for (auto m : masks)
{
//Check length here
for (int j = 0; j < m.length(); j++)
if (o->genome[j] != m[j]) counts++;
}
return counts;
}
//Need to add mask in some flavours of EE
bool Environment::addMask()
{
masks.append(QVector <bool>());
int newMask = masks.length() - 1;
for (int i = 0; i < masks[0].length(); i++)
{
//This will generate a random integer between 0 (inclusive) and 2 (exclusive) - so either 0 or 1. Break it out for clarity
int random = QRandomGenerator::global()->bounded(0, 2);
if (random == 0) masks[newMask].append(bool(false));
else if (random == 1) masks[newMask].append(bool(true));
else return false; //This should never happen
}
return true;
}
//For EE we need to overwrite a mask
void Environment::overwriteMask(Organism const *o)
{
//EE works either by copying over genome to a prexisting mask - thus improving fitness of the EE species - or to the mask added
//Either way, we can write over the last mask for each environment (-1 because indexing starts at zero)
//We always do this on the last one since this was just added if we're adding a mask rather than just overwriting (makes no difference if not adding one)
for (int i = 0; i < masks[masks.length() - 1].length(); i++) masks[masks.length() - 1][i] = o->genome[i];
}
bool Environment::compareOrganism(Organism const *o)
{
for (auto &m : masks)
if (o->genome == m) return true;
return false;
}
int Environment::countDifferences(Environment const &externalEnvironment)
{
int maskCount = masks.length();
int bitCount = masks[0].length();
if ((maskCount != externalEnvironment.masks.length()) || (bitCount != externalEnvironment.masks[0].length())) return -1;
int count = 0;
for (int m = 0; m < masks.length(); m++)
for (int b = 0; b < masks[m].length(); b++)
if (masks[m][b] != externalEnvironment.masks[m][b]) count++;
return count;
}
int Environment::bitCount()
{
int count = 0;
for (auto m : masks)
for (auto b : m)
if (b) count++;
return count;
}
void Environment::setUpPerturbation(int startIteration, int endIteration)
{
environmentalPerturbationMasksCopy = masks;
for (int i = 0; i < masks.length(); i++) environmentalPerturbationOverwriting.append(QVector <bool>(masks[0].length(), "FALSE"));
//Need to copy over 90% of original masks over course of perturbation
environmentalPerturbationCopyRate = masks[0].length() * masks.length();
environmentalPerturbationCopyRate /= 10;
environmentalPerturbationCopyRate *= 9;
perturbationStart = startIteration;
perturbationEnd = endIteration;
//Bork current masks to create environmental perturbation
for (int j = 0; j < masks.length(); j++)
for (int i = 0; i < masks[j].length(); i++)
{
//This will generate a random integer between 0 (inclusive) and 2 (exclusive) - so either 0 or 1. Break it out for clarity
int random = QRandomGenerator::global()->bounded(0, 2);
if (random == 0) masks[j][i] = false;
else if (random == 1) masks[j][i] = true;
}
}
void Environment::applyPerturbation(int currentIteration)
{
int copied = 0;
do
{
//Random number size of vectors
int j = QRandomGenerator::global()->bounded(masks.length());
int i = QRandomGenerator::global()->bounded(masks[0].length());
//Update
if (environmentalPerturbationOverwriting[j][i] == false)
{
environmentalPerturbationOverwriting[j][i] = true;
copied++;
}
}
while (copied < environmentalPerturbationCopyRate);
//Copy over masks where dictated by the the overwriting record
for (int j = 0; j < masks.length(); j++)
for (int i = 0; i < masks[0].length(); i++)
if (environmentalPerturbationOverwriting[j][i])
masks[j][i] = environmentalPerturbationMasksCopy[j][i];
}