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

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

Implemented crossing over, handled failed mutations and crossovers, added printing extensive statistics

File size: 6.3 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
13
14struct Individual
15{
16        Geno geno;
17        double fitness;
18};
19
20double criterion(char symbol, double value)
21{
22        return isupper(symbol) ? value : -value;
23}
24
25double get_fitness(const Individual &ind, const char *fitness_def)
26{
27        SString genotype = ind.geno.getGenes();
28        Model model = Model(ind.geno, Model::SHAPETYPE_UNKNOWN);
29        double fitness = 0;
30        const char *p = fitness_def;
31        while (*p)
32        {
33                switch (*p)
34                {
35                case '0':
36                        break;
37                case '!': //special symbol for current fitness (used only in printing population stats)
38                        fitness += ind.fitness;
39                        break;
40                case 'l':
41                case 'L':
42                        fitness += criterion(*p, genotype.length());
43                        break;
44                case 'p':
45                case 'P':
46                        fitness += criterion(*p, model.getPartCount());
47                        break;
48                case 'j':
49                case 'J':
50                        fitness += criterion(*p, model.getJointCount());
51                        break;
52                case 'n':
53                case 'N':
54                        fitness += criterion(*p, model.getNeuroCount());
55                        break;
56                case 'c':
57                case 'C':
58                        fitness += criterion(*p, model.getConnectionCount());
59                        break;
60                        // TODO add more criteria as described in main() below
61                default:
62                        printf("Unknown fitness criterion symbol: '%c'\n", *p);
63                        exit(3);
64                }
65                p++;
66        }
67        return fitness;
68}
69
70void update_fitness(Individual &ind, const char *fitness_def)
71{
72        ind.fitness = get_fitness(ind, fitness_def);
73}
74
75void print_stats(const vector<Individual> &population, char criterion)
76{
77        vector<double> criterion_values;
78        char crit[2] = { 0 };
79        crit[0] = criterion;
80        for (const Individual& ind : population)
81                criterion_values.push_back(get_fitness(ind, crit));
82        printf("%g,%g,%g", *std::min_element(criterion_values.begin(), criterion_values.end()),
83                std::accumulate(criterion_values.begin(), criterion_values.end(), 0.0) / criterion_values.size(),
84                *std::max_element(criterion_values.begin(), criterion_values.end()));
85}
86
87int tournament(const vector<Individual> &population, int tournament_size)
88{
89        int best = -1;
90        for (int i = 0; i < tournament_size; i++)
91        {
92                int rnd = rndUint(population.size());
93                if (best == -1) best = rnd;
94                else if (population[rnd].fitness > population[best].fitness) //assume maximization
95                        best = rnd;
96        }
97        return best;
98}
99
100
101// A minimalistic steady-state evolutionary algorithm.
102int main(int argc, char *argv[])
103{
104        PreconfiguredGenetics genetics;
105        LoggerToStdout messages_to_stdout(LoggerBase::Enable);
106        GenMan genman;
107
108        bool deterministic;
109        int pop_size, nr_evals;
110        double prob_mut, prob_xover;
111        const char* format;
112        const char* fitness_def;
113
114        if (argc < 8)
115        {
116                printf("Too few parameters!\n");
117                printf("Command line: <deterministic?_0_or_1> <population_size> <nr_evaluations> <prob_mut> <prob_xover> <genetic_format> <fitness_definition>\n");
118                printf("Example: 1 10 50 0.6 0.4 4 NC\n\n");
119                printf("Fitness definition is a sequence of capital (+1 weight) and small (-1 weight) letters.\n");
120                printf("Each letter corresponds to one fitness criterion, and they are all weighted and added together.\n");
121                printf("  0      - a constant value of 0 that provides a flat fitness landscape (e.g. for testing biases of genetic operators).\n");
122                printf("  l or L - genotype length in characters.\n");
123                printf("  p or P - the number of Parts.\n");
124                printf("  j or J - the number of Joints.\n");
125                printf("  n or N - the number of Neurons.\n");
126                printf("  c or C - the number of neural Connections.\n");
127                //TODO add b - bounding box volume (from Model), s - surface area (from geometry), v - volume (from geometry), h,w,d - three consecutive dimensions (from geometry)
128
129                printf("\nThe output contains 7 columns separated by the TAB character.\n");
130                printf("The first column is the number of mutated or crossed over and evaluated genotypes.\n");
131                printf("The remaining columns are triplets of min,avg,max (in the population) of fitness, Parts, Joints, Neurons, Connections, genotype characters.\n");
132                printf("Finally, the genotypes in the last population are printed with their fitness values.\n");
133                return 1;
134        }
135
136        deterministic = atoi(argv[1]) == 1;
137        pop_size = atoi(argv[2]);
138        nr_evals = atoi(argv[3]);
139        prob_mut = atof(argv[4]);
140        prob_xover = atof(argv[5]);
141        format = argv[6];
142        fitness_def = argv[7];
143
144        if (!deterministic)
145                rndGetInstance().randomize();
146
147        vector<Individual> population(pop_size);
148        for (Individual& ind : population)
149        {
150                ind.geno = genman.getSimplest(format);
151                if (ind.geno.getGenes() == "")
152                {
153                        printf("Could not get the simplest genotype for format '%s'\n", format);
154                        return 2;
155                }
156                update_fitness(ind, fitness_def);
157        }
158        for (int i = 0; i < nr_evals; i++)
159        {
160                int selected_positive = tournament(population, max(2, int(sqrt(population.size()) / 2))); //moderate positive selection pressure
161                int selected_negative = rndUint(population.size()); //random negative selection
162
163                double rnd = rndDouble(prob_mut + prob_xover);
164                if (rnd < prob_mut)
165                {
166                        Geno mutant = genman.mutate(population[selected_positive].geno);
167                        if (mutant.getGenes() == "")
168                        {
169                                printf("Failed mutation (%s) of '%s'\n", mutant.getComment().c_str(), population[selected_positive].geno.getGenes().c_str());
170                        }
171                        else
172                        {
173                                population[selected_negative].geno = mutant;
174                                update_fitness(population[selected_negative], fitness_def);
175                        }
176                }
177                else
178                {
179                        int selected_positive2 = tournament(population, max(2, int(sqrt(population.size()) / 2)));
180                        Geno xover = genman.crossOver(population[selected_positive].geno, population[selected_positive2].geno);
181                        if (xover.getGenes() == "")
182                        {
183                                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());
184                        }
185                        else
186                        {
187                                population[selected_negative].geno = xover;
188                                update_fitness(population[selected_negative], fitness_def);
189                        }
190                }
191
192                if (i % population.size() == 0 || i == nr_evals - 1)
193                {
194                        printf("Evaluation %d\t", i);
195                        for (char c : string("!PJNCL"))
196                        {
197                                print_stats(population, c);
198                                printf("\t");
199                        }
200                        printf("\n");
201                }
202        }
203        for (const Individual& ind : population)
204        {
205                printf("%.1f\t", ind.fitness);
206                printf("%s\n", ind.geno.getGenes().c_str());
207        }
208
209        return 0;
210}
Note: See TracBrowser for help on using the repository browser.