[1007] | 1 | // This file is a part of Framsticks SDK. http://www.framsticks.com/ |
---|
[1130] | 2 | // Copyright (C) 2019-2021 Maciej Komosinski and Szymon Ulatowski. |
---|
[1007] | 3 | // See LICENSE.txt for details. |
---|
| 4 | |
---|
| 5 | |
---|
| 6 | #include <vector> |
---|
[1008] | 7 | #include <numeric> //std::accumulate() |
---|
[1007] | 8 | #include "common/loggers/loggertostdout.h" |
---|
| 9 | #include "frams/genetics/preconfigured.h" |
---|
| 10 | #include "frams/genetics/genman.h" |
---|
| 11 | #include "frams/model/model.h" |
---|
[1031] | 12 | #include "frams/model/geometry/modelgeometryinfo.h" |
---|
[1007] | 13 | |
---|
| 14 | |
---|
| 15 | struct Individual |
---|
| 16 | { |
---|
| 17 | Geno geno; |
---|
| 18 | double fitness; |
---|
| 19 | }; |
---|
| 20 | |
---|
| 21 | double criterion(char symbol, double value) |
---|
| 22 | { |
---|
| 23 | return isupper(symbol) ? value : -value; |
---|
| 24 | } |
---|
| 25 | |
---|
[1008] | 26 | double get_fitness(const Individual &ind, const char *fitness_def) |
---|
[1007] | 27 | { |
---|
[1031] | 28 | const double GEOM_DENSITY = 5.0; //needs testing and adjusting as needed - tradeoff between precision and speed |
---|
| 29 | |
---|
[1007] | 30 | SString genotype = ind.geno.getGenes(); |
---|
| 31 | Model model = Model(ind.geno, Model::SHAPETYPE_UNKNOWN); |
---|
| 32 | double fitness = 0; |
---|
| 33 | const char *p = fitness_def; |
---|
[1031] | 34 | |
---|
| 35 | Orient axes; |
---|
| 36 | Pt3D sizes; |
---|
[1007] | 37 | while (*p) |
---|
| 38 | { |
---|
[1031] | 39 | switch (*p) |
---|
| 40 | { |
---|
| 41 | case '0': |
---|
| 42 | break; |
---|
| 43 | case '!': //special symbol for current fitness (used only in printing population stats) |
---|
| 44 | fitness += ind.fitness; |
---|
| 45 | break; |
---|
| 46 | case 'g': |
---|
| 47 | case 'G': |
---|
| 48 | fitness += criterion(*p, genotype.length()); |
---|
| 49 | break; |
---|
| 50 | case 'p': |
---|
| 51 | case 'P': |
---|
| 52 | fitness += criterion(*p, model.getPartCount()); |
---|
| 53 | break; |
---|
| 54 | case 'j': |
---|
| 55 | case 'J': |
---|
| 56 | fitness += criterion(*p, model.getJointCount()); |
---|
| 57 | break; |
---|
| 58 | case 'n': |
---|
| 59 | case 'N': |
---|
| 60 | fitness += criterion(*p, model.getNeuroCount()); |
---|
| 61 | break; |
---|
| 62 | case 'c': |
---|
| 63 | case 'C': |
---|
| 64 | fitness += criterion(*p, model.getConnectionCount()); |
---|
| 65 | break; |
---|
| 66 | case 'b': |
---|
| 67 | case 'B': |
---|
| 68 | fitness += criterion(*p, model.size.x * model.size.y * model.size.z); |
---|
| 69 | break; |
---|
| 70 | case 's': |
---|
| 71 | case 'S': |
---|
| 72 | fitness += criterion(*p, ModelGeometryInfo::area(model, GEOM_DENSITY)); |
---|
| 73 | break; |
---|
| 74 | case 'v': |
---|
| 75 | case 'V': |
---|
| 76 | fitness += criterion(*p, ModelGeometryInfo::volume(model, GEOM_DENSITY)); |
---|
| 77 | break; |
---|
| 78 | case 'l': |
---|
| 79 | case 'L': |
---|
| 80 | ModelGeometryInfo::findSizesAndAxes(model, GEOM_DENSITY, sizes, axes); |
---|
| 81 | fitness += criterion(*p, sizes.x); |
---|
| 82 | break; |
---|
| 83 | case 'w': |
---|
| 84 | case 'W': |
---|
| 85 | ModelGeometryInfo::findSizesAndAxes(model, GEOM_DENSITY, sizes, axes); |
---|
| 86 | fitness += criterion(*p, sizes.y); |
---|
| 87 | break; |
---|
| 88 | case 'h': |
---|
| 89 | case 'H': |
---|
| 90 | ModelGeometryInfo::findSizesAndAxes(model, GEOM_DENSITY, sizes, axes); |
---|
| 91 | fitness += criterion(*p, sizes.z); |
---|
| 92 | break; |
---|
| 93 | default: |
---|
| 94 | printf("Unknown fitness criterion symbol: '%c'\n", *p); |
---|
| 95 | exit(3); |
---|
[1007] | 96 | } |
---|
| 97 | p++; |
---|
| 98 | } |
---|
[1008] | 99 | return fitness; |
---|
[1007] | 100 | } |
---|
| 101 | |
---|
[1008] | 102 | void update_fitness(Individual &ind, const char *fitness_def) |
---|
| 103 | { |
---|
| 104 | ind.fitness = get_fitness(ind, fitness_def); |
---|
| 105 | } |
---|
| 106 | |
---|
| 107 | void print_stats(const vector<Individual> &population, char criterion) |
---|
| 108 | { |
---|
| 109 | vector<double> criterion_values; |
---|
| 110 | char crit[2] = { 0 }; |
---|
| 111 | crit[0] = criterion; |
---|
| 112 | for (const Individual& ind : population) |
---|
| 113 | criterion_values.push_back(get_fitness(ind, crit)); |
---|
| 114 | printf("%g,%g,%g", *std::min_element(criterion_values.begin(), criterion_values.end()), |
---|
[1031] | 115 | std::accumulate(criterion_values.begin(), criterion_values.end(), 0.0) / criterion_values.size(), |
---|
| 116 | *std::max_element(criterion_values.begin(), criterion_values.end())); |
---|
[1008] | 117 | } |
---|
| 118 | |
---|
[1007] | 119 | int tournament(const vector<Individual> &population, int tournament_size) |
---|
| 120 | { |
---|
| 121 | int best = -1; |
---|
| 122 | for (int i = 0; i < tournament_size; i++) |
---|
| 123 | { |
---|
| 124 | int rnd = rndUint(population.size()); |
---|
| 125 | if (best == -1) best = rnd; |
---|
| 126 | else if (population[rnd].fitness > population[best].fitness) //assume maximization |
---|
| 127 | best = rnd; |
---|
| 128 | } |
---|
| 129 | return best; |
---|
| 130 | } |
---|
| 131 | |
---|
| 132 | |
---|
[1008] | 133 | // A minimalistic steady-state evolutionary algorithm. |
---|
[1007] | 134 | int main(int argc, char *argv[]) |
---|
| 135 | { |
---|
| 136 | PreconfiguredGenetics genetics; |
---|
| 137 | LoggerToStdout messages_to_stdout(LoggerBase::Enable); |
---|
| 138 | GenMan genman; |
---|
| 139 | |
---|
| 140 | bool deterministic; |
---|
| 141 | int pop_size, nr_evals; |
---|
| 142 | double prob_mut, prob_xover; |
---|
| 143 | const char* format; |
---|
| 144 | const char* fitness_def; |
---|
| 145 | |
---|
| 146 | if (argc < 8) |
---|
| 147 | { |
---|
| 148 | printf("Too few parameters!\n"); |
---|
| 149 | printf("Command line: <deterministic?_0_or_1> <population_size> <nr_evaluations> <prob_mut> <prob_xover> <genetic_format> <fitness_definition>\n"); |
---|
[1008] | 150 | printf("Example: 1 10 50 0.6 0.4 4 NC\n\n"); |
---|
[1007] | 151 | printf("Fitness definition is a sequence of capital (+1 weight) and small (-1 weight) letters.\n"); |
---|
| 152 | printf("Each letter corresponds to one fitness criterion, and they are all weighted and added together.\n"); |
---|
[1008] | 153 | printf(" 0 - a constant value of 0 that provides a flat fitness landscape (e.g. for testing biases of genetic operators).\n"); |
---|
[1031] | 154 | printf(" g or G - genotype length in characters.\n"); |
---|
| 155 | printf(" p or P - number of Parts.\n"); |
---|
| 156 | printf(" j or J - number of Joints.\n"); |
---|
| 157 | printf(" n or N - number of Neurons.\n"); |
---|
| 158 | printf(" c or C - number of neural Connections.\n"); |
---|
| 159 | printf(" b or B - volume of the bounding box (absolute coordinates).\n"); |
---|
| 160 | printf(" s or S - surface area of the Model.\n"); |
---|
| 161 | printf(" v or V - volume of the Model.\n"); |
---|
| 162 | printf(" l or L - length of the Model (largest dimension).\n"); |
---|
| 163 | printf(" w or W - width of the Model (2nd largest dimension).\n"); |
---|
| 164 | printf(" h or H - height of the Model (smallest dimension).\n"); |
---|
[1007] | 165 | |
---|
[1009] | 166 | printf("\nThe output consists of 7 columns separated by the TAB character.\n"); |
---|
[1008] | 167 | printf("The first column is the number of mutated or crossed over and evaluated genotypes.\n"); |
---|
| 168 | printf("The remaining columns are triplets of min,avg,max (in the population) of fitness, Parts, Joints, Neurons, Connections, genotype characters.\n"); |
---|
| 169 | printf("Finally, the genotypes in the last population are printed with their fitness values.\n"); |
---|
[1007] | 170 | return 1; |
---|
| 171 | } |
---|
| 172 | |
---|
| 173 | deterministic = atoi(argv[1]) == 1; |
---|
| 174 | pop_size = atoi(argv[2]); |
---|
| 175 | nr_evals = atoi(argv[3]); |
---|
| 176 | prob_mut = atof(argv[4]); |
---|
| 177 | prob_xover = atof(argv[5]); |
---|
| 178 | format = argv[6]; |
---|
| 179 | fitness_def = argv[7]; |
---|
| 180 | |
---|
| 181 | if (!deterministic) |
---|
| 182 | rndGetInstance().randomize(); |
---|
| 183 | |
---|
| 184 | vector<Individual> population(pop_size); |
---|
| 185 | for (Individual& ind : population) |
---|
| 186 | { |
---|
| 187 | ind.geno = genman.getSimplest(format); |
---|
| 188 | if (ind.geno.getGenes() == "") |
---|
| 189 | { |
---|
| 190 | printf("Could not get the simplest genotype for format '%s'\n", format); |
---|
| 191 | return 2; |
---|
| 192 | } |
---|
[1008] | 193 | update_fitness(ind, fitness_def); |
---|
[1007] | 194 | } |
---|
| 195 | for (int i = 0; i < nr_evals; i++) |
---|
| 196 | { |
---|
[1130] | 197 | int selected_positive = tournament(population, std::max(2, int(sqrt(population.size()) / 2))); //moderate positive selection pressure |
---|
[1007] | 198 | int selected_negative = rndUint(population.size()); //random negative selection |
---|
| 199 | |
---|
| 200 | double rnd = rndDouble(prob_mut + prob_xover); |
---|
| 201 | if (rnd < prob_mut) |
---|
| 202 | { |
---|
[1008] | 203 | Geno mutant = genman.mutate(population[selected_positive].geno); |
---|
| 204 | if (mutant.getGenes() == "") |
---|
| 205 | { |
---|
| 206 | printf("Failed mutation (%s) of '%s'\n", mutant.getComment().c_str(), population[selected_positive].geno.getGenes().c_str()); |
---|
| 207 | } |
---|
| 208 | else |
---|
| 209 | { |
---|
| 210 | population[selected_negative].geno = mutant; |
---|
| 211 | update_fitness(population[selected_negative], fitness_def); |
---|
| 212 | } |
---|
[1007] | 213 | } |
---|
| 214 | else |
---|
| 215 | { |
---|
[1130] | 216 | int selected_positive2 = tournament(population, std::max(2, int(sqrt(population.size()) / 2))); |
---|
[1008] | 217 | Geno xover = genman.crossOver(population[selected_positive].geno, population[selected_positive2].geno); |
---|
| 218 | if (xover.getGenes() == "") |
---|
| 219 | { |
---|
| 220 | printf("Failed crossover (%s) of '%s' and '%s'\n", xover.getComment().c_str(), population[selected_positive].geno.getGenes().c_str(), population[selected_positive2].geno.getGenes().c_str()); |
---|
| 221 | } |
---|
| 222 | else |
---|
| 223 | { |
---|
| 224 | population[selected_negative].geno = xover; |
---|
| 225 | update_fitness(population[selected_negative], fitness_def); |
---|
| 226 | } |
---|
[1007] | 227 | } |
---|
| 228 | |
---|
| 229 | if (i % population.size() == 0 || i == nr_evals - 1) |
---|
[1008] | 230 | { |
---|
[1009] | 231 | printf("Evaluation %d", i); |
---|
[1031] | 232 | for (char c : string("!PJNCG")) |
---|
| 233 | { |
---|
[1009] | 234 | printf("\t"); |
---|
[1008] | 235 | print_stats(population, c); |
---|
| 236 | } |
---|
| 237 | printf("\n"); |
---|
| 238 | } |
---|
[1007] | 239 | } |
---|
| 240 | for (const Individual& ind : population) |
---|
| 241 | { |
---|
| 242 | printf("%.1f\t", ind.fitness); |
---|
[1009] | 243 | printf("%s\n", ind.geno.getGenesAndFormat().c_str()); |
---|
[1007] | 244 | } |
---|
| 245 | |
---|
| 246 | return 0; |
---|
| 247 | } |
---|