source: cpp/frams/genetics/f4/f4_oper.cpp @ 1314

Last change on this file since 1314 was 1313, checked in by Maciej Komosinski, 4 months ago

Color mutations in f1 and f4, and a new syntax for "allowed modifiers" (opposite to previous "excluded modifiers") with optional probabilities for each modifier

  • Property svn:eol-style set to native
File size: 29.9 KB
Line 
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
40static 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
64Geno_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
83void Geno_f4::setDefaults()
84{
85        allowed_modifiers = F14_MODIFIERS_BASIC F14_MODIFIERS_COLOR_SPORADIC;
86}
87
88int 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
120int 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
139int 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
156int 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
466vector<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
483bool 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
497void 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
508void 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
516void 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
524int 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;
564retm1v:
565        return res;
566}
567
568
569int 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/*
593int Geno_f4::MutateMany(char *& g, float & chg)
594// check if original is valid, then
595// make a number of mutations
596{
597int res, n, i;
598int totNodes = 0;
599int maxToMut = 0;
600
601// convert to tree
602f4_Node *root;
603root = new f4_Node();
604res = f4_processrec(g, 0, root);
605if (res) {
606// could not convert, fail
607goto retm;
608}
609if (1 != root->childCount()) {
610res = GENOPER_OPFAIL;
611goto retm;
612}
613
614// check if original is valid
615res = ValidateRec( root, 20 );
616// might have been repaired!
617if (GENOPER_REPAIR==res) {
618res = GENOPER_OK;
619}
620if (GENOPER_OK != res) {
621goto retm;
622}
623
624// decide number of nodes to mutate
625// decide maximum number of nodes to mutate: 0.25*nodes, min 2
626totNodes = root->child->count();
627maxToMut = (int)( 0.25f * totNodes);
628if (maxToMut<2) maxToMut=2;
629if (maxToMut>totNodes) maxToMut=totNodes;
630
631// decide number of nodes to mutate
632n = (int)( 0.5 + rndDouble(1) * maxToMut );
633if (n<1) n=1;
634if (n>totNodes) n=totNodes;
635// set chg as this percent
636chg = ((float)n) / ((float)totNodes);
637for (i=0; i<n; i++)
638{
639res = MutateOneValid(root);
640if (GENOPER_OK != res)
641{
642res = GENOPER_OPFAIL;
643goto retm;
644}
645}
646// OK, convert back to string
647g[0]=0;
648root->child->sprintAdj(g);
649retm:
650delete root;
651return res;
652}
653*/
654
655
656int 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
691int 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
715uint32_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}
Note: See TracBrowser for help on using the repository browser.