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 | |
---|
14 | struct Individual |
---|
15 | { |
---|
16 | Geno geno; |
---|
17 | double fitness; |
---|
18 | }; |
---|
19 | |
---|
20 | double criterion(char symbol, double value) |
---|
21 | { |
---|
22 | return isupper(symbol) ? value : -value; |
---|
23 | } |
---|
24 | |
---|
25 | double 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 | |
---|
70 | void update_fitness(Individual &ind, const char *fitness_def) |
---|
71 | { |
---|
72 | ind.fitness = get_fitness(ind, fitness_def); |
---|
73 | } |
---|
74 | |
---|
75 | void 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 | |
---|
87 | int 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. |
---|
102 | int 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 consists of 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", i); |
---|
195 | for (char c : string("!PJNCL"))
|
---|
196 | {
|
---|
197 | printf("\t"); |
---|
198 | print_stats(population, c); |
---|
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.getGenesAndFormat().c_str()); |
---|
207 | } |
---|
208 | |
---|
209 | return 0; |
---|
210 | } |
---|