1 | // This file is a part of Framsticks SDK. http://www.framsticks.com/ |
---|
2 | // Copyright (C) 1999-2024 Maciej Komosinski and Szymon Ulatowski. |
---|
3 | // See LICENSE.txt for details. |
---|
4 | |
---|
5 | // Copyright (C) 1999,2000 Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL |
---|
6 | // Copyright (C) since 2001 Maciej Komosinski |
---|
7 | // 2018, Grzegorz Latosinski, added development checkpoints and support for new API for neuron types |
---|
8 | |
---|
9 | |
---|
10 | // This representation has a tendency to bloat - adding a small penalty to fitness such as "this.velocity - 0.000000001*String.len(this.genotype);" may help, but it would be better to improve the source code to make genetic operators neutral in terms of genotype length. Adding such a penalty removes "work in progress" changes in genotypes thus promoting immediate, straightforward improvements while hindering slower, multifaceted progress. |
---|
11 | // TODO getting rid of redundancy (valid genotypes with a lot of "junk code") in this representation looks like a good idea; many improvements to this end have already been done in April & May 2023, so maybe it is not a big problem now? |
---|
12 | // |
---|
13 | // |
---|
14 | // TODO the behavior of neuron input indexes during mutation seems badly implemented (see also TREAT_BAD_CONNECTIONS_AS_INVALID_GENO). Are they kept properly maintained when nodes are added and removed? This could be done well because during mutation we operate on the tree structure with cross-references between nodes (so they should not be affected by local changes in the tree), and then convert the tree back to string. Yet, the f4_Node.conn_from is an integer and these fields in nodes do not seem to be maintained on tree node adding/removal... change these integer offsets to references to node objects? But actually, do the offsets that constitute relative connection references concern the f4_Node tree structure (and all these sophisticated calculations of offsets during mutation are useful) or rather they concern the f4_Cells development? verify all situations in f4_Cell::oneStep(), case '['. |
---|
15 | // TODO in mutation, adding the '#' gene does not seem to be effective. The gene is added and genotypes are valid, but hardly ever #n is effective, i.e., it hardly ever multiplicates body or brain parts... investigate! Maybe most places it is added at are ineffective. And maybe, during repair or mutations, simplify/remove ineffective #N genes, if there is no chance/hardly any chance that they will be turned effective after future mutation (or crossover) |
---|
16 | // TODO add support for properties of (any class of) neurons - not just sigmoid/force/intertia (':' syntax) for N |
---|
17 | // TODO add mapping genotype character ranges for neural [connections] |
---|
18 | // TODO The f0 genotypes for /*4*/<<RX>X>X> and RX(X,X) are identical, but if you replace R with C or Q, there are small differences (they were present both before and after the change in C,Q effects in the f1 converter in 2023-06, see conv_f1_f0_cq_influence) - check why (modifiers affecting cells=sticks are applied differently or skip some initial sticks?) and perhaps unify with f1? |
---|
19 | // TODO F4_SIMPLIFY_MODIFIERS in f4_general.cpp: currently it works while parsing (which is a bit "cheating": we get a phenotype that is a processed version of the genotype, thus some changes in modifiers in the genotype have no effect on its phenotype). This is especially noticeable with color modifiers, where simplification only accepts MAX_NUMBER_SAME_TYPE_COLOR. Another (likely better) option, instead of simplifying while parsing, would be during mutations (like it is done in f1): when mutations add/modify/remove a modifier node, they could "clean" the tree by simplifying modifiers on the same subpath just as GenoOperators::simplifiedModifiers() does. This way, simplifying would be only performed when we actually modify a part of a genotype, not each time we interpret it, and there would be no hidden mechanism: all visible genes would have an expected effect on the phenotype. In other words, if a genotype happened to have more modifiers of the same type than allowed by GenoOperators::simplifiedModifiers(), they would all be effective (like in f1) and not silently ignored when interpreting that genotype. For example in /*4*/GGGgGX> due to simplifying modifiers (removing the oldest), only the last G remains effective when MAX_NUMBER_SAME_TYPE_COLOR=1, and it is impossible to adjust colors precisely even by manually editing the genotype - like it is in f1, even though the same simplifying function is used, just in a different moment. |
---|
20 | // TODO improve the way modifiers are handled in the f4->f1 approximate converter (used extremely rarely just for illustration) |
---|
21 | |
---|
22 | |
---|
23 | |
---|
24 | #include "f4_oper.h" |
---|
25 | #include <frams/util/sstring.h> |
---|
26 | #include <common/log.h> |
---|
27 | |
---|
28 | #include <stdio.h> |
---|
29 | #include <stdlib.h> |
---|
30 | #include "common/nonstd_math.h" |
---|
31 | #include <string.h> |
---|
32 | |
---|
33 | |
---|
34 | |
---|
35 | // codes that can be changed (apart from being added/deleted) |
---|
36 | #define F4_MUT_CHANGE_CODES "<[#" |
---|
37 | |
---|
38 | #define FIELDSTRUCT Geno_f4 |
---|
39 | |
---|
40 | static ParamEntry geno_f4_paramtab[] = |
---|
41 | { |
---|
42 | { "Genetics: f4", 1, F4_COUNT + F4_ADD_COUNT + F4_MODNEU_COUNT + 2, }, |
---|
43 | { "f4_mut_add", 0, 0, "Add node", "f 0 100 4", FIELD(prob[F4_ADD]), "Mutation: probability of adding a node", }, |
---|
44 | { "f4_mut_add_div", 0, 0, "- add division", "f 0 100 4", FIELD(probadd[F4_ADD_DIV]), "Add node mutation: probability of adding a division", }, |
---|
45 | { "f4_mut_add_conn", 0, 0, "- add connection", "f 0 100 1", FIELD(probadd[F4_ADD_CONN]), "Add node mutation: probability of adding a neural connection", }, |
---|
46 | { "f4_mut_add_neupar", 0, 0, "- add neuron property", "f 0 100 1", FIELD(probadd[F4_ADD_NEUPAR]), "Add node mutation: probability of adding a neuron property/modifier", }, |
---|
47 | { "f4_mut_add_rep", 0, 0, "- add repetition '#'", "f 0 100 1", FIELD(probadd[F4_ADD_REP]), "Add node mutation: probability of adding the '#' repetition gene", }, |
---|
48 | { "f4_mut_add_simp", 0, 0, "- add simple node", "f 0 100 4", FIELD(probadd[F4_ADD_SIMP]), "Add node mutation: probability of adding a random, simple gene", }, |
---|
49 | |
---|
50 | { "f4_mut_del", 0, 0, "Delete node", "f 0 100 1", FIELD(prob[F4_DEL]), "Mutation: probability of deleting a node", }, |
---|
51 | |
---|
52 | { "f4_mut_mod", 0, 0, "Modify node", "f 0 100 1", FIELD(prob[F4_MOD]), "Mutation: probability of changing a node", }, |
---|
53 | { "f4_mut_modneu_conn", 0, 0, "- neuron input: modify source", "f 0 100 3", FIELD(probmodneu[F4_MODNEU_CONN]), "Neuron input mutation: probability of changing its source neuron", }, |
---|
54 | { "f4_mut_modneu_weight", 0, 0, "- neuron input: modify weight", "f 0 100 3", FIELD(probmodneu[F4_MODNEU_WEIGHT]), "Neuron input mutation: probability of changing its weight", }, |
---|
55 | |
---|
56 | { "f4_mut_max_rep", 1, 0, "Maximum number for '#' repetitions", "d 2 20 6", FIELD(mut_max_rep), "Maximum allowed number of repetitions for the '#' repetition gene", }, |
---|
57 | { "f4_mut_modifiers", 1, 0, "Allowed modifiers", "s 0 100", FIELD(allowed_modifiers), "Modifier symbols that will be added or deleted during mutation\n(from the full set: " F14_MODIFIERS ").\n\nYou may use the extended syntax: after every allowed symbol, you may include its probability value in parentheses.\nWithout parentheses, all allowed symbols behave as if they had (1.0) appended.\nIf you include (0.0) after a symbol, this bans that symbol as if it was not present in this string.", }, |
---|
58 | { 0, }, |
---|
59 | }; |
---|
60 | |
---|
61 | #undef FIELDSTRUCT |
---|
62 | |
---|
63 | |
---|
64 | Geno_f4::Geno_f4() |
---|
65 | { |
---|
66 | supported_format = '4'; |
---|
67 | par.setParamTab(geno_f4_paramtab); |
---|
68 | par.select(this); |
---|
69 | par.setDefault(); |
---|
70 | |
---|
71 | mutation_method_names = new const char*[F4_COUNT + F4_ADD_COUNT - 1]; |
---|
72 | int index = 0; |
---|
73 | mutation_method_names[index++] = "added division"; |
---|
74 | mutation_method_names[index++] = "added neural connection"; |
---|
75 | mutation_method_names[index++] = "added neuron property"; |
---|
76 | mutation_method_names[index++] = "added repetition gene"; |
---|
77 | mutation_method_names[index++] = "added a simple node"; |
---|
78 | mutation_method_names[index++] = "deleted a node"; |
---|
79 | mutation_method_names[index++] = "modified a node"; |
---|
80 | if (index != F4_COUNT + F4_ADD_COUNT - 1) logMessage("Geno_f4", "Constructor", LOG_CRITICAL, "Mutation names init error"); |
---|
81 | } |
---|
82 | |
---|
83 | void Geno_f4::setDefaults() |
---|
84 | { |
---|
85 | allowed_modifiers = F14_MODIFIERS_BASIC F14_MODIFIERS_COLOR_SPORADIC; |
---|
86 | } |
---|
87 | |
---|
88 | int Geno_f4::ValidateRecur(f4_Node *geno, int retrycount) const |
---|
89 | { |
---|
90 | // ! the genotype is geno->child (not geno) ! |
---|
91 | // build from it with repair on |
---|
92 | |
---|
93 | f4_Cells cells(geno->child, true); |
---|
94 | cells.simulate(); //we should simulate?! |
---|
95 | |
---|
96 | // errors not fixed: |
---|
97 | if (cells.getErrorCode() == GENOPER_OPFAIL) |
---|
98 | { |
---|
99 | if (cells.getErrorPos() >= 0) return 1 + cells.getErrorPos(); |
---|
100 | return GENOPER_OPFAIL; |
---|
101 | } |
---|
102 | // errors can be fixed |
---|
103 | if (cells.getErrorCode() == GENOPER_REPAIR) |
---|
104 | { |
---|
105 | cells.repairGeno(geno, 1); |
---|
106 | // note: geno might have been fixed |
---|
107 | // check again |
---|
108 | int res2 = GENOPER_OK; |
---|
109 | if (retrycount > 0) |
---|
110 | res2 = ValidateRecur(geno, retrycount - 1); |
---|
111 | |
---|
112 | if (res2 == GENOPER_OK) return GENOPER_REPAIR; |
---|
113 | return res2; |
---|
114 | } |
---|
115 | // no errors: |
---|
116 | return GENOPER_OK; |
---|
117 | } |
---|
118 | |
---|
119 | |
---|
120 | int Geno_f4::validate(char *& geno, const char *genoname) |
---|
121 | { |
---|
122 | // convert geno to a tree, then try to validate |
---|
123 | f4_Node root; |
---|
124 | int res = f4_process(geno, &root); |
---|
125 | if (root.childCount() != 1) return GENOPER_OK; // the resulting tree will not be repairable (fatal flaw; root must have exactly one child) - do not even attempt repair |
---|
126 | |
---|
127 | // here we have a genotype with root.childCount()==1 (meaning some part was successfully parsed into a tree) and either res==0 (syntax was correct, semantics we don't know) or res>0 (for sure has some error) |
---|
128 | const int VALIDATE_TRIALS = 20; |
---|
129 | res = ValidateRecur(&root, VALIDATE_TRIALS); |
---|
130 | if (res != GENOPER_OPFAIL) // if repaired (GENOPER_REPAIR) or had no errors (GENOPER_OK, e.g. the genotype had some errors that were ignored during tree creation or had junk genes appended at the end, so the tree was OK but the genotype was not), |
---|
131 | { |
---|
132 | geno[0] = 0; |
---|
133 | root.child->sprintAdj(geno); //make it back to string |
---|
134 | } |
---|
135 | return GENOPER_OK; |
---|
136 | } |
---|
137 | |
---|
138 | |
---|
139 | int Geno_f4::checkValidity(const char* geno, const char *genoname) |
---|
140 | { |
---|
141 | f4_Node root; |
---|
142 | int res = f4_process(geno, &root); |
---|
143 | if (res) return res; // errorpos, >0 |
---|
144 | if (root.childCount() != 1) return 1; // fatal flaw; root must have exactly one child |
---|
145 | f4_Cells cells(root.child, false); |
---|
146 | cells.simulate(); |
---|
147 | if (cells.getErrorCode() == GENOPER_OPFAIL || cells.getErrorCode() == GENOPER_REPAIR) |
---|
148 | { |
---|
149 | if (cells.getErrorPos() >= 0) return 1 + cells.getErrorPos(); |
---|
150 | else return 1; //error, no known position |
---|
151 | } |
---|
152 | else return GENOPER_OK; |
---|
153 | } |
---|
154 | |
---|
155 | |
---|
156 | int Geno_f4::MutateOne(f4_Node *& g, int &method) const |
---|
157 | { |
---|
158 | // ! the genotype is g->child (not g) ! |
---|
159 | |
---|
160 | // do the mutation |
---|
161 | // pick a random node |
---|
162 | f4_Node *node_mutated = g->child->randomNode(); |
---|
163 | //DB( printf("%c\n", node_mutated->name); ) |
---|
164 | |
---|
165 | switch (roulette(prob, F4_COUNT)) |
---|
166 | { |
---|
167 | case F4_ADD: |
---|
168 | { |
---|
169 | // add a node |
---|
170 | switch (method = roulette(probadd, F4_ADD_COUNT)) |
---|
171 | { |
---|
172 | case F4_ADD_DIV: |
---|
173 | { |
---|
174 | // add division ('<') |
---|
175 | f4_Node *node_mutated_parent = node_mutated->parent; |
---|
176 | node_mutated_parent->removeChild(node_mutated); |
---|
177 | f4_Node *node_new_div = new f4_Node('<', node_mutated_parent, node_mutated_parent->pos); |
---|
178 | node_new_div->addChild(node_mutated); |
---|
179 | // new cell is stick or neuron |
---|
180 | // "X>" or "N>" |
---|
181 | constexpr double STICK_OR_NEURON = 0.5; // hardcoded probability... could be parametrized, but in a general case (unknown fitness goal) 0.5 makes sense? |
---|
182 | f4_Node *node_new = NULL; //stick or neuron or neural connection |
---|
183 | if (rndDouble(1) < STICK_OR_NEURON) |
---|
184 | { |
---|
185 | node_new = new f4_Node('X', node_new_div, node_new_div->pos); |
---|
186 | //now add one color modifier before this new X, just as in f1's Geno_f1::addMutationColoredX() |
---|
187 | if (prob[F4_ADD] > 0 && probadd[F4_ADD_SIMP] > 0) //user wants modifier mutations, so we are allowed to prepend a random color modifier to "X" |
---|
188 | { |
---|
189 | char color_mod = GenoOperators::getRandomColorModifier(allowed_modifiers.c_str(), F14_MODIFIERS_COLOR); //may return 0 if no color modifiers available |
---|
190 | if (color_mod != 0) |
---|
191 | { |
---|
192 | //adding a new color modifier node just as in the F4_ADD_SIMP case later below. |
---|
193 | node_new->parent->removeChild(node_new); |
---|
194 | f4_Node *n2 = new f4_Node(color_mod, node_new->parent, node_new->parent->pos); |
---|
195 | n2->addChild(node_new); |
---|
196 | node_new->parent = n2; |
---|
197 | } |
---|
198 | } |
---|
199 | } |
---|
200 | else |
---|
201 | { |
---|
202 | // make neuron |
---|
203 | NeuroClass *rndclass = GenoOperators::getRandomNeuroClass(Model::SHAPETYPE_BALL_AND_STICK); |
---|
204 | if (rndclass == NULL) //no active neurons? |
---|
205 | { |
---|
206 | node_new = new f4_Node('X', node_new_div, node_new_div->pos); |
---|
207 | } |
---|
208 | else |
---|
209 | { |
---|
210 | f4_Node *node_new_neuron = new f4_Node(rndclass->getName().c_str(), node_new_div, node_new_div->pos); |
---|
211 | node_new_neuron->neuclass = rndclass; |
---|
212 | node_new = node_new_neuron; //can be changed below if all goes well and we add a new connection too |
---|
213 | if (probadd[F4_ADD_CONN] > 0) //user wants to add connections |
---|
214 | { |
---|
215 | if (rndclass->getPreferredInputs() != 0) //neuron also wants connections? |
---|
216 | { |
---|
217 | int node_new_neuron_index, other_neuron_index; |
---|
218 | bool ok = findConnectionNeuronIndexes(g, node_new_neuron, true, node_new_neuron_index, other_neuron_index); //node_new_neuron_index==-1 should never happen, we just added node_new_neuron we are looking for |
---|
219 | if (ok) //we can create a new connection |
---|
220 | { |
---|
221 | node_new = new f4_Node('[', node_new_neuron, node_new_div->pos); |
---|
222 | connectionNodeChangeRandom(node_new, node_new_neuron_index, other_neuron_index); |
---|
223 | } |
---|
224 | } |
---|
225 | else if (rndclass->getPreferredOutput() > 0) //neuron also wants connections? |
---|
226 | { |
---|
227 | // Not so easy: we would need to add a '[' node as a child not of node_new_neuron, but of other neuron that would get an input from node_new_neuron (and need to properly calculate relative connection reference). |
---|
228 | // The "false" argument in findConnectionNeuronIndexes() below is not suffient, because we also need to access (find) the f4_Node of the other neuron. |
---|
229 | // A similar logic is implemented in F4_ADD_CONN below, but let's not complicate this F4_ADD_DIV mutation anymore. |
---|
230 | // The disadvantage is that the node_new_neuron added here which is a neuron that provides output (e.g., a receptor, N, etc.) will not get connected immediately here even when there are already existing neurons wanting inputs (e.g., muscles, N, etc.). |
---|
231 | //bool ok = findConnectionNeuronIndexes(g, ... , false, ..., ...); |
---|
232 | } |
---|
233 | } |
---|
234 | } |
---|
235 | } |
---|
236 | new f4_Node('>', node_new, node_new->pos); //adds '>' to node_new |
---|
237 | node_mutated->parent = node_new_div; |
---|
238 | // now, swap children with 50% chance |
---|
239 | if (rndUint(2) == 0) |
---|
240 | { |
---|
241 | node_mutated_parent = node_new_div->child; |
---|
242 | node_new_div->child = node_new_div->child2; |
---|
243 | node_new_div->child2 = node_mutated_parent; |
---|
244 | } |
---|
245 | } |
---|
246 | break; |
---|
247 | case F4_ADD_CONN: |
---|
248 | { |
---|
249 | // add connection |
---|
250 | |
---|
251 | // the probability that a randomly selected node will be a neuron and additionally this neuron will accept inputs is low, |
---|
252 | // so we disregard randomly picked node_mutated and build a list of all valid candidate nodes here, then randomly select one from them. |
---|
253 | |
---|
254 | vector<f4_Node*> candidate_nodes; //neurons that accept input(s) |
---|
255 | for (int i = 0; i < g->count(); i++) |
---|
256 | { |
---|
257 | f4_Node *node = g->ordNode(i); |
---|
258 | f4_Node *node_parent = node->parent; |
---|
259 | if (node_parent == NULL || node_parent->neuclass == NULL) continue; |
---|
260 | int prefinputs = node_parent->neuclass->getPreferredInputs(); |
---|
261 | if (prefinputs == -1 || |
---|
262 | prefinputs > 0) //would be nice if we could easily and quickly check if the parent already has its preferred inputs used, so that we do not produce an invalid mutation here... it is possible through the f4_Cell.n_conns field, but only during organism development |
---|
263 | candidate_nodes.push_back(node); |
---|
264 | } |
---|
265 | |
---|
266 | if (candidate_nodes.size() == 0) |
---|
267 | return GENOPER_OPFAIL; |
---|
268 | |
---|
269 | node_mutated = candidate_nodes[rndUint(candidate_nodes.size())]; |
---|
270 | f4_Node *node_mutated_parent = node_mutated->parent; |
---|
271 | |
---|
272 | int node_mutated_parent_index, other_neuron_index; |
---|
273 | bool ok = findConnectionNeuronIndexes(g, node_mutated_parent, true, node_mutated_parent_index, other_neuron_index); //node_mutated_parent_index==-1 should never happen, we earlier selected the neuron we are now looking for |
---|
274 | if (!ok) |
---|
275 | return GENOPER_OPFAIL; |
---|
276 | |
---|
277 | node_mutated->parent->removeChild(node_mutated); //this subtree will be reconnected below, as a child to node_new_conn |
---|
278 | f4_Node *node_new_conn = new f4_Node('[', node_mutated->parent, node_mutated->parent->pos); |
---|
279 | node_new_conn->addChild(node_mutated); |
---|
280 | node_mutated->parent = node_new_conn; // node_mutated_parent is the neuron, node_mutated->parent is '[' |
---|
281 | connectionNodeChangeRandom(node_new_conn, node_mutated_parent_index, other_neuron_index); |
---|
282 | } |
---|
283 | break; |
---|
284 | case F4_ADD_NEUPAR: |
---|
285 | { |
---|
286 | // add neuron modifier |
---|
287 | node_mutated->parent->removeChild(node_mutated); |
---|
288 | f4_Node *n2 = new f4_Node(':', node_mutated->parent, node_mutated->parent->pos); |
---|
289 | nparNodeMakeRandom(n2); |
---|
290 | n2->addChild(node_mutated); |
---|
291 | node_mutated->parent = n2; |
---|
292 | } |
---|
293 | break; |
---|
294 | case F4_ADD_REP: |
---|
295 | { |
---|
296 | // add repetition ('#') |
---|
297 | // repeated code (left child) is the original, right child is empty, count is set to 2 |
---|
298 | f4_Node *n3 = node_mutated->parent; |
---|
299 | n3->removeChild(node_mutated); |
---|
300 | f4_Node *n2 = new f4_Node('#', n3, n3->pos); |
---|
301 | n2->reps = 2; |
---|
302 | n2->addChild(node_mutated); |
---|
303 | new f4_Node('>', n2, n2->pos); |
---|
304 | node_mutated->parent = n2; |
---|
305 | } |
---|
306 | break; |
---|
307 | case F4_ADD_SIMP: |
---|
308 | { |
---|
309 | // add simple node |
---|
310 | SString allowed_modifiers_and_comma = allowed_modifiers + ","; // traditionally, in f4, comma was treated here equally with modifiers |
---|
311 | char modifier = GenoOperators::getRandomModifier(allowed_modifiers_and_comma.c_str()); |
---|
312 | if (modifier == 0) |
---|
313 | return GENOPER_OPFAIL; |
---|
314 | node_mutated->parent->removeChild(node_mutated); |
---|
315 | // old source: choose a simple node from ADD_SIMPLE_CODES |
---|
316 | //f4_Node *n2 = new f4_Node(ADD_SIMPLE_CODES[rndUint(strlen(ADD_SIMPLE_CODES))], node_mutated->parent, node_mutated->parent->pos); |
---|
317 | f4_Node *n2 = new f4_Node(modifier, node_mutated->parent, node_mutated->parent->pos); |
---|
318 | n2->addChild(node_mutated); |
---|
319 | node_mutated->parent = n2; |
---|
320 | } |
---|
321 | break; |
---|
322 | } |
---|
323 | } |
---|
324 | break; |
---|
325 | |
---|
326 | case F4_DEL: |
---|
327 | { |
---|
328 | method = F4_ADD_COUNT - 1 + F4_DEL; |
---|
329 | // delete a node |
---|
330 | // must pick a node with parent, and at least one child |
---|
331 | // already picked a node, but repeat may be needed |
---|
332 | for (int i = 0; i < 10; i++) |
---|
333 | { |
---|
334 | if ((node_mutated->parent != NULL) && (g != node_mutated->parent)) |
---|
335 | if (node_mutated->child != NULL) |
---|
336 | break; |
---|
337 | // try a new one |
---|
338 | node_mutated = g->child->randomNode(); |
---|
339 | } |
---|
340 | if ((node_mutated->parent != NULL) && (g != node_mutated->parent)) |
---|
341 | { |
---|
342 | switch (node_mutated->childCount()) |
---|
343 | { |
---|
344 | case 0: break; |
---|
345 | case 1: // one child |
---|
346 | { |
---|
347 | f4_Node *node_mutated_parent = node_mutated->parent; |
---|
348 | node_mutated_parent->removeChild(node_mutated); |
---|
349 | if (node_mutated->child != NULL) |
---|
350 | { |
---|
351 | node_mutated->child->parent = node_mutated_parent; |
---|
352 | node_mutated_parent->addChild(node_mutated->child); |
---|
353 | node_mutated->child = NULL; |
---|
354 | } |
---|
355 | if (node_mutated->child2 != NULL) |
---|
356 | { |
---|
357 | node_mutated->child2->parent = node_mutated_parent; |
---|
358 | node_mutated_parent->addChild(node_mutated->child2); |
---|
359 | node_mutated->child2 = NULL; |
---|
360 | } |
---|
361 | // destroy n1 |
---|
362 | node_mutated->parent = NULL; |
---|
363 | delete node_mutated; |
---|
364 | } |
---|
365 | break; |
---|
366 | |
---|
367 | case 2: // two children |
---|
368 | { |
---|
369 | // two children |
---|
370 | f4_Node *n2 = node_mutated->parent; |
---|
371 | n2->removeChild(node_mutated); |
---|
372 | // n1 has two children. pick one randomly 50-50, destroy other |
---|
373 | if (rndUint(2) == 0) |
---|
374 | { |
---|
375 | node_mutated->child->parent = n2; |
---|
376 | n2->addChild(node_mutated->child); |
---|
377 | node_mutated->child = NULL; |
---|
378 | node_mutated->child2->parent = NULL; |
---|
379 | } |
---|
380 | else |
---|
381 | { |
---|
382 | node_mutated->child2->parent = n2; |
---|
383 | n2->addChild(node_mutated->child2); |
---|
384 | node_mutated->child2 = NULL; |
---|
385 | node_mutated->child->parent = NULL; |
---|
386 | } |
---|
387 | // destroy n1 |
---|
388 | node_mutated->parent = NULL; |
---|
389 | delete node_mutated; |
---|
390 | } |
---|
391 | break; |
---|
392 | } |
---|
393 | } |
---|
394 | else return GENOPER_OPFAIL; |
---|
395 | } |
---|
396 | break; |
---|
397 | case F4_MOD: |
---|
398 | { |
---|
399 | method = F4_ADD_COUNT - 1 + F4_MOD; |
---|
400 | // change a node |
---|
401 | // the only nodes that are modifiable are F4_MUT_CHANGE_CODES |
---|
402 | // try to get a modifiable node |
---|
403 | // already picked a node, but repeat may be needed |
---|
404 | int i = 0; |
---|
405 | while (1) |
---|
406 | { |
---|
407 | if (strchr(F4_MUT_CHANGE_CODES, node_mutated->name[0])) break; |
---|
408 | // try a new one |
---|
409 | node_mutated = g->child->randomNode(); |
---|
410 | i++; |
---|
411 | if (i >= 20) return GENOPER_OPFAIL; |
---|
412 | } |
---|
413 | switch (node_mutated->name[0]) |
---|
414 | { |
---|
415 | case '<': |
---|
416 | { |
---|
417 | // swap children |
---|
418 | f4_Node *n2 = node_mutated->child; |
---|
419 | node_mutated->child = node_mutated->child2; |
---|
420 | node_mutated->child2 = n2; |
---|
421 | } |
---|
422 | break; |
---|
423 | case '[': |
---|
424 | { |
---|
425 | switch (roulette(probmodneu, F4_MODNEU_COUNT)) |
---|
426 | { |
---|
427 | case F4_MODNEU_CONN: |
---|
428 | { |
---|
429 | f4_Node *neuron = node_mutated; //we start in '[' node and follow up parents until we find the neuron with these connections |
---|
430 | while (neuron != NULL && neuron->neuclass == NULL) neuron = neuron->parent; |
---|
431 | if (neuron == NULL) |
---|
432 | return GENOPER_OPFAIL; //did not find a neuron on the way up tree |
---|
433 | |
---|
434 | |
---|
435 | int neuron_index, other_neuron_index; |
---|
436 | bool ok = findConnectionNeuronIndexes(g, neuron, true, neuron_index, other_neuron_index); //neuron_index==-1 should never happen, we know the neuron is in the tree |
---|
437 | if (!ok) |
---|
438 | return GENOPER_OPFAIL; |
---|
439 | |
---|
440 | connectionNodeChangeRandom(node_mutated, neuron_index, other_neuron_index); |
---|
441 | break; |
---|
442 | } |
---|
443 | case F4_MODNEU_WEIGHT: |
---|
444 | node_mutated->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(node_mutated->conn_weight); |
---|
445 | break; |
---|
446 | } |
---|
447 | } |
---|
448 | break; |
---|
449 | |
---|
450 | case '#': |
---|
451 | { |
---|
452 | repeatNodeChangeRandom(node_mutated); |
---|
453 | } |
---|
454 | break; |
---|
455 | } |
---|
456 | } |
---|
457 | break; |
---|
458 | |
---|
459 | default: //no mutations allowed? |
---|
460 | return GENOPER_OPFAIL; |
---|
461 | } |
---|
462 | return GENOPER_OK; |
---|
463 | } |
---|
464 | |
---|
465 | // find all neurons and the needle |
---|
466 | vector<NeuroClass*> Geno_f4::findAllNeuronsAndNode(f4_Node * const & g, f4_Node* const &needle_neuron, int &found_index) |
---|
467 | { |
---|
468 | found_index = -1; // not found (for example, needle_neuron is not a neuroclass node or not added to the "g" tree) |
---|
469 | vector<NeuroClass*> neulist; |
---|
470 | for (int i = 0; i < g->count(); i++) |
---|
471 | { |
---|
472 | f4_Node *node = g->ordNode(i); |
---|
473 | if (node->neuclass != NULL) |
---|
474 | { |
---|
475 | neulist.push_back(node->neuclass); |
---|
476 | if (node == needle_neuron) |
---|
477 | found_index = int(neulist.size()) - 1; |
---|
478 | } |
---|
479 | } |
---|
480 | return neulist; |
---|
481 | } |
---|
482 | |
---|
483 | bool Geno_f4::findConnectionNeuronIndexes(f4_Node * const &g, f4_Node *neuron, bool other_has_output, int &neuron_index, int &other_neuron_index) |
---|
484 | { |
---|
485 | vector<NeuroClass*> neulist = findAllNeuronsAndNode(g, neuron, neuron_index); |
---|
486 | if (neuron_index == -1) |
---|
487 | return false; |
---|
488 | |
---|
489 | other_neuron_index = other_has_output ? |
---|
490 | GenoOperators::getRandomNeuroClassWithOutput(neulist) //find an existing neuron that provides an output |
---|
491 | : |
---|
492 | GenoOperators::getRandomNeuroClassWithInput(neulist); //find an existing neuron that accepts input(s) |
---|
493 | return other_neuron_index >= 0; |
---|
494 | } |
---|
495 | |
---|
496 | // change a [ node |
---|
497 | void Geno_f4::connectionNodeChangeRandom(f4_Node *nn, int nn_index, int other_index) const |
---|
498 | { |
---|
499 | // relative input connection to some existing neuron |
---|
500 | nn->conn_from = nn_index - other_index; |
---|
501 | //nn->conn_from = (int)(4.0f * (rndDouble(1) - 0.5)); //in very old times - did not care about neuron input/output preferences |
---|
502 | |
---|
503 | nn->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(nn->conn_weight); |
---|
504 | } |
---|
505 | |
---|
506 | |
---|
507 | // make a random : node |
---|
508 | void Geno_f4::nparNodeMakeRandom(f4_Node *nn) const |
---|
509 | { |
---|
510 | unsigned int prop = rndUint(3); //random neuron property |
---|
511 | nn->prop_symbol = "!=/"[prop]; |
---|
512 | nn->prop_increase = rndUint(2) == 1; |
---|
513 | } |
---|
514 | |
---|
515 | // change a repeat # node |
---|
516 | void Geno_f4::repeatNodeChangeRandom(f4_Node *nn) const |
---|
517 | { |
---|
518 | if (rndDouble(1) < 0.5) nn->reps++; else nn->reps--; // change count |
---|
519 | if (nn->reps < 1) nn->reps = 1; |
---|
520 | if (nn->reps > mut_max_rep) nn->reps = mut_max_rep; |
---|
521 | } |
---|
522 | |
---|
523 | |
---|
524 | int Geno_f4::MutateOneValid(f4_Node *& g, int &method) const |
---|
525 | // mutate one, until a valid genotype is obtained |
---|
526 | { |
---|
527 | // ! the genotype is g->child (not g) ! |
---|
528 | int i, res; |
---|
529 | f4_Node *gcopy = NULL; |
---|
530 | const int TRY_MUTATE = 20; |
---|
531 | // try this at most TRY_MUTATE times: copy, mutate, then validate |
---|
532 | for (i = 0; i < TRY_MUTATE; i++) |
---|
533 | { |
---|
534 | gcopy = g->duplicate(); |
---|
535 | |
---|
536 | res = MutateOne(gcopy, method); |
---|
537 | |
---|
538 | if (GENOPER_OK != res) |
---|
539 | { |
---|
540 | // mutation failed, try again |
---|
541 | delete gcopy; |
---|
542 | continue; // for |
---|
543 | } |
---|
544 | // try to validate it |
---|
545 | res = ValidateRecur(gcopy, 10); |
---|
546 | // accept if it is OK, or was repaired |
---|
547 | if (GENOPER_OK == res) |
---|
548 | //(GENOPER_REPAIR == res) |
---|
549 | { |
---|
550 | // destroy the original one |
---|
551 | g->destroy(); |
---|
552 | // make it the new one |
---|
553 | *g = *gcopy; |
---|
554 | gcopy->child = NULL; |
---|
555 | gcopy->child2 = NULL; |
---|
556 | delete gcopy; |
---|
557 | res = GENOPER_OK; |
---|
558 | goto retm1v; |
---|
559 | } |
---|
560 | delete gcopy; |
---|
561 | } |
---|
562 | // attempts failed |
---|
563 | res = GENOPER_OPFAIL; |
---|
564 | retm1v: |
---|
565 | return res; |
---|
566 | } |
---|
567 | |
---|
568 | |
---|
569 | int Geno_f4::mutate(char *& g, float & chg, int &method) |
---|
570 | { |
---|
571 | f4_Node *root = new f4_Node; |
---|
572 | if (f4_process(g, root) || root->childCount() != 1) |
---|
573 | { |
---|
574 | delete root; |
---|
575 | return GENOPER_OPFAIL; |
---|
576 | } // could not convert or bad: fail |
---|
577 | // mutate one node, set chg as this percent |
---|
578 | chg = 1.0 / float(root->child->count()); |
---|
579 | if (MutateOneValid(root, method) != GENOPER_OK) |
---|
580 | { |
---|
581 | delete root; |
---|
582 | return GENOPER_OPFAIL; |
---|
583 | } |
---|
584 | // OK, convert back to string |
---|
585 | g[0] = 0; |
---|
586 | root->child->sprintAdj(g); |
---|
587 | delete root; |
---|
588 | return GENOPER_OK; |
---|
589 | } |
---|
590 | |
---|
591 | |
---|
592 | /* |
---|
593 | int Geno_f4::MutateMany(char *& g, float & chg) |
---|
594 | // check if original is valid, then |
---|
595 | // make a number of mutations |
---|
596 | { |
---|
597 | int res, n, i; |
---|
598 | int totNodes = 0; |
---|
599 | int maxToMut = 0; |
---|
600 | |
---|
601 | // convert to tree |
---|
602 | f4_Node *root; |
---|
603 | root = new f4_Node(); |
---|
604 | res = f4_processrec(g, 0, root); |
---|
605 | if (res) { |
---|
606 | // could not convert, fail |
---|
607 | goto retm; |
---|
608 | } |
---|
609 | if (1 != root->childCount()) { |
---|
610 | res = GENOPER_OPFAIL; |
---|
611 | goto retm; |
---|
612 | } |
---|
613 | |
---|
614 | // check if original is valid |
---|
615 | res = ValidateRec( root, 20 ); |
---|
616 | // might have been repaired! |
---|
617 | if (GENOPER_REPAIR==res) { |
---|
618 | res = GENOPER_OK; |
---|
619 | } |
---|
620 | if (GENOPER_OK != res) { |
---|
621 | goto retm; |
---|
622 | } |
---|
623 | |
---|
624 | // decide number of nodes to mutate |
---|
625 | // decide maximum number of nodes to mutate: 0.25*nodes, min 2 |
---|
626 | totNodes = root->child->count(); |
---|
627 | maxToMut = (int)( 0.25f * totNodes); |
---|
628 | if (maxToMut<2) maxToMut=2; |
---|
629 | if (maxToMut>totNodes) maxToMut=totNodes; |
---|
630 | |
---|
631 | // decide number of nodes to mutate |
---|
632 | n = (int)( 0.5 + rndDouble(1) * maxToMut ); |
---|
633 | if (n<1) n=1; |
---|
634 | if (n>totNodes) n=totNodes; |
---|
635 | // set chg as this percent |
---|
636 | chg = ((float)n) / ((float)totNodes); |
---|
637 | for (i=0; i<n; i++) |
---|
638 | { |
---|
639 | res = MutateOneValid(root); |
---|
640 | if (GENOPER_OK != res) |
---|
641 | { |
---|
642 | res = GENOPER_OPFAIL; |
---|
643 | goto retm; |
---|
644 | } |
---|
645 | } |
---|
646 | // OK, convert back to string |
---|
647 | g[0]=0; |
---|
648 | root->child->sprintAdj(g); |
---|
649 | retm: |
---|
650 | delete root; |
---|
651 | return res; |
---|
652 | } |
---|
653 | */ |
---|
654 | |
---|
655 | |
---|
656 | int Geno_f4::CrossOverOne(f4_Node *g1, f4_Node *g2, float chg) const |
---|
657 | { |
---|
658 | // ! the genotypes are g1->child and g2->child (not g1 g2) ! |
---|
659 | // single offspring in g1 |
---|
660 | int smin, smax; |
---|
661 | float size; |
---|
662 | f4_Node *n1, *n2, *n1p, *n2p; |
---|
663 | |
---|
664 | // determine desired size |
---|
665 | size = (1 - chg) * (float)g1->count(); |
---|
666 | smin = (int)(size * 0.9f - 1); |
---|
667 | smax = (int)(size * 1.1f + 1); |
---|
668 | // get a random node with desired size |
---|
669 | n1 = g1->child->randomNodeWithSize(smin, smax); |
---|
670 | |
---|
671 | // determine desired size |
---|
672 | size = (1 - chg) * (float)g2->count(); |
---|
673 | smin = (int)(size * 0.9f - 1); |
---|
674 | smax = (int)(size * 1.1f + 1); |
---|
675 | // get a random node with desired size |
---|
676 | n2 = g2->child->randomNodeWithSize(smin, smax); |
---|
677 | |
---|
678 | // exchange the two nodes: |
---|
679 | n1p = n1->parent; |
---|
680 | n2p = n2->parent; |
---|
681 | n1p->removeChild(n1); |
---|
682 | n1p->addChild(n2); |
---|
683 | n2p->removeChild(n2); |
---|
684 | n2p->addChild(n1); |
---|
685 | n1->parent = n2p; |
---|
686 | n2->parent = n1p; |
---|
687 | |
---|
688 | return GENOPER_OK; |
---|
689 | } |
---|
690 | |
---|
691 | int Geno_f4::crossOver(char *&g1, char *&g2, float &chg1, float &chg2) |
---|
692 | { |
---|
693 | f4_Node root1, root2, *copy1, *copy2; |
---|
694 | |
---|
695 | // convert genotype strings into tree structures |
---|
696 | if (f4_process(g1, &root1) || (root1.childCount() != 1)) return GENOPER_OPFAIL; |
---|
697 | if (f4_process(g2, &root2) || (root2.childCount() != 1)) return GENOPER_OPFAIL; |
---|
698 | |
---|
699 | // decide amounts of crossover, 0.1-0.9 |
---|
700 | chg1 = 0.1 + rndDouble(0.8); |
---|
701 | chg2 = 0.1 + rndDouble(0.8); |
---|
702 | |
---|
703 | copy1 = root1.duplicate(); |
---|
704 | if (CrossOverOne(copy1, &root2, chg1) != GENOPER_OK) { delete copy1; copy1 = NULL; } |
---|
705 | copy2 = root2.duplicate(); |
---|
706 | if (CrossOverOne(copy2, &root1, chg2) != GENOPER_OK) { delete copy2; copy2 = NULL; } |
---|
707 | |
---|
708 | g1[0] = 0; |
---|
709 | g2[0] = 0; |
---|
710 | if (copy1) { copy1->child->sprintAdj(g1); delete copy1; } |
---|
711 | if (copy2) { copy2->child->sprintAdj(g2); delete copy2; } |
---|
712 | if (g1[0] || g2[0]) return GENOPER_OK; else return GENOPER_OPFAIL; |
---|
713 | } |
---|
714 | |
---|
715 | uint32_t Geno_f4::style(const char *g, int pos) |
---|
716 | { |
---|
717 | char ch = g[pos]; |
---|
718 | |
---|
719 | // style categories |
---|
720 | #define STYL4CAT_MODIFIC F14_MODIFIERS "," |
---|
721 | #define STYL4CAT_NEUMOD "/!=" |
---|
722 | #define STYL4CAT_NEUSPECIAL "|@*" |
---|
723 | #define STYL4CAT_DIGIT "+-0123456789.[]" //'+' is only for adjusting old-style properties "/!=" |
---|
724 | #define STYL4CAT_REST ":XN<># " |
---|
725 | |
---|
726 | if (!isalpha(ch) && !strchr(STYL4CAT_MODIFIC STYL4CAT_NEUMOD STYL4CAT_NEUSPECIAL STYL4CAT_DIGIT STYL4CAT_REST "\t", ch)) |
---|
727 | { |
---|
728 | return GENSTYLE_CS(0, GENSTYLE_INVALID); |
---|
729 | } |
---|
730 | uint32_t style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT); //default, should be changed below |
---|
731 | if (strchr("X", ch)) style = GENSTYLE_CS(0, GENSTYLE_BOLD); |
---|
732 | else if (strchr(":", ch)) style = GENSTYLE_CS(0, GENSTYLE_NONE); |
---|
733 | else if (strchr("#", ch)) style = GENSTYLE_RGBS(220, 0, 0, GENSTYLE_BOLD); |
---|
734 | else if (strchr("/=!", ch)) style = GENSTYLE_RGBS(255, 140, 0, GENSTYLE_BOLD); //property... for now, f4 does not supoprt properties in general for any neuron class, like f1 does |
---|
735 | else if (strchr("N@|*", ch)) style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); //neuroclass |
---|
736 | else if (strchr("<", ch)) style = GENSTYLE_RGBS(0, 0, 200, GENSTYLE_BOLD); |
---|
737 | else if (strchr(">", ch)) style = GENSTYLE_RGBS(0, 0, 100, GENSTYLE_NONE); |
---|
738 | else if (strchr(STYL4CAT_DIGIT, ch)) style = GENSTYLE_CS(GENCOLOR_NUMBER, GENSTYLE_NONE); |
---|
739 | else if (strchr(STYL4CAT_MODIFIC, ch)) |
---|
740 | { |
---|
741 | if (strchr(F14_MODIFIERS_COLOR, ch)) //color modifier - less important so less visible on white |
---|
742 | style = GENSTYLE_RGBS(200, 200, 200, GENSTYLE_NONE); |
---|
743 | else //non-color modifier |
---|
744 | style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE); |
---|
745 | } |
---|
746 | else if (strchr(STYL4CAT_NEUMOD, ch)) style = GENSTYLE_RGBS(0, 150, 0, GENSTYLE_NONE); |
---|
747 | if (isalpha(ch)) |
---|
748 | { |
---|
749 | // allowed neuron formats: |
---|
750 | // N:CLASSNAME |
---|
751 | // N:@ |
---|
752 | // N:| |
---|
753 | // old syntax still supported in coloring, but no longer valid: |
---|
754 | // [SENSOR, WEIGHT] |
---|
755 | // N@ |
---|
756 | // N| |
---|
757 | // ...so must have N: or [ before neuroclass name (or just N, but this is handled above - for N@|* only) |
---|
758 | |
---|
759 | while (pos > 0) |
---|
760 | { |
---|
761 | pos--; |
---|
762 | if (!isalpha(g[pos])) |
---|
763 | { |
---|
764 | if (isupper(g[pos + 1]) && (g[pos] == '[') || (g[pos] == ':' && pos > 0 && g[pos - 1] == 'N')) //we may have sequences like :-/:I (even though they are not valid) - in this example "I" should not be treated as neuron name, hence there must also be a "N" before ":" |
---|
765 | style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); // neuroclass |
---|
766 | //(...) else (...) |
---|
767 | // style = GENSTYLE_RGBS(255, 140, 0, GENSTYLE_BOLD); // property - current f4 does not support neuron properties in a general case, only those old-style "/=!" as +! -! += -= +/ -/ |
---|
768 | break; |
---|
769 | } |
---|
770 | } |
---|
771 | } |
---|
772 | return style; |
---|
773 | } |
---|