source: cpp/frams/_demos/evol_test.cpp @ 1045

Last change on this file since 1045 was 1031, checked in by Maciej Komosinski, 4 years ago

Added a number of model geometry criteria to evolution

File size: 7.4 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 2019-2020  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5
6#include <vector>
7#include <numeric> //std::accumulate()
8#include "common/loggers/loggertostdout.h"
9#include "frams/genetics/preconfigured.h"
10#include "frams/genetics/genman.h"
11#include "frams/model/model.h"
12#include "frams/model/geometry/modelgeometryinfo.h"
13
14
15struct Individual
16{
17        Geno geno;
18        double fitness;
19};
20
21double criterion(char symbol, double value)
22{
23        return isupper(symbol) ? value : -value;
24}
25
26double get_fitness(const Individual &ind, const char *fitness_def)
27{
28        const double GEOM_DENSITY = 5.0; //needs testing and adjusting as needed - tradeoff between precision and speed
29
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;
34
35        Orient axes;
36        Pt3D sizes;
37        while (*p)
38        {
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);
96                }
97                p++;
98        }
99        return fitness;
100}
101
102void update_fitness(Individual &ind, const char *fitness_def)
103{
104        ind.fitness = get_fitness(ind, fitness_def);
105}
106
107void 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()),
115                   std::accumulate(criterion_values.begin(), criterion_values.end(), 0.0) / criterion_values.size(),
116                   *std::max_element(criterion_values.begin(), criterion_values.end()));
117}
118
119int 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
133// A minimalistic steady-state evolutionary algorithm.
134int 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");
150                printf("Example: 1 10 50 0.6 0.4 4 NC\n\n");
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");
153                printf("  0      - a constant value of 0 that provides a flat fitness landscape (e.g. for testing biases of genetic operators).\n");
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");
165
166                printf("\nThe output consists of 7 columns separated by the TAB character.\n");
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");
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                }
193                update_fitness(ind, fitness_def);
194        }
195        for (int i = 0; i < nr_evals; i++)
196        {
197                int selected_positive = tournament(population, max(2, int(sqrt(population.size()) / 2))); //moderate positive selection pressure
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                {
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                        }
213                }
214                else
215                {
216                        int selected_positive2 = tournament(population, max(2, int(sqrt(population.size()) / 2)));
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                        }
227                }
228
229                if (i % population.size() == 0 || i == nr_evals - 1)
230                {
231                        printf("Evaluation %d", i);
232                        for (char c : string("!PJNCG"))
233                        {
234                                printf("\t");
235                                print_stats(population, c);
236                        }
237                        printf("\n");
238                }
239        }
240        for (const Individual& ind : population)
241        {
242                printf("%.1f\t", ind.fitness);
243                printf("%s\n", ind.geno.getGenesAndFormat().c_str());
244        }
245
246        return 0;
247}
Note: See TracBrowser for help on using the repository browser.