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

Last change on this file since 1259 was 1259, checked in by Maciej Komosinski, 11 months ago

f4: three #define's -> enum, minor refactorizations, added comments

  • Property svn:eol-style set to native
File size: 28.2 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2023  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);"
11// 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
12// removes "work in progress" changes in genotypes thus promoting immediate, straightforward improvements while hindering slower, multifaceted progress.
13// 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?
14//
15//
16// 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 '['.
17// 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)
18// TODO add support for properties of (any class of) neurons - not just sigmoid/force/intertia (':' syntax) for N
19// TODO add mapping genotype character ranges for neural [connections]
20// 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?
21// 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). 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.
22// TODO improve the way modifiers are handled in the f4->f1 approximate converter (used extremely rarely just for illustration)
23
24
25
26#include "f4_oper.h"
27#include <frams/util/sstring.h>
28#include <common/log.h>
29
30#include <stdio.h>
31#include <stdlib.h>
32#include "common/nonstd_math.h"
33#include <string.h>
34
35
36const char *Geno_f4::all_modifiers = F14_MODIFIERS ","; //comma in f4 is handled the same way (simple node, F4_ADD_SIMP) as modifiers. See also all_modifiers_no_comma in f4_general.cpp.
37
38// codes that can be changed (apart from being added/deleted)
39#define F4_MUT_CHANGE_CODES "<[#"
40
41#define FIELDSTRUCT Geno_f4
42
43static ParamEntry geno_f4_paramtab[] =
44{
45        { "Genetics: f4", 1, F4_COUNT + F4_ADD_COUNT + F4_MODNEU_COUNT + 2, },
46        { "f4_mut_add", 0, 0, "Add node", "f 0 100 4", FIELD(prob[F4_ADD]), "Mutation: probability of adding a node", },
47        { "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", },
48        { "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", },
49        { "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", },
50        { "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", },
51        { "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", },
52
53        { "f4_mut_del", 0, 0, "Delete node", "f 0 100 1", FIELD(prob[F4_DEL]), "Mutation: probability of deleting a node", },
54
55        { "f4_mut_mod", 0, 0, "Modify node", "f 0 100 1", FIELD(prob[F4_MOD]), "Mutation: probability of changing a node", },
56        { "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", },
57        { "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", },
58
59        { "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", },
60        { "f4_mut_exmod", 1, 0, "Excluded modifiers", "s 0 30", FIELD(excluded_modifiers), "Modifiers that will not be added nor deleted during mutation\n(all: " F14_MODIFIERS ")", },
61        { 0, },
62};
63
64#undef FIELDSTRUCT
65
66
67Geno_f4::Geno_f4()
68{
69        supported_format = '4';
70        par.setParamTab(geno_f4_paramtab);
71        par.select(this);
72        par.setDefault();
73
74        mutation_method_names = new const char*[F4_COUNT + F4_ADD_COUNT - 1];
75        int index = 0;
76        mutation_method_names[index++] = "added division";
77        mutation_method_names[index++] = "added neural connection";
78        mutation_method_names[index++] = "added neuron property";
79        mutation_method_names[index++] = "added repetition gene";
80        mutation_method_names[index++] = "added a simple node";
81        mutation_method_names[index++] = "deleted a node";
82        mutation_method_names[index++] = "modified a node";
83        if (index != F4_COUNT + F4_ADD_COUNT - 1) logMessage("Geno_f4", "Constructor", LOG_CRITICAL, "Mutation names init error");
84}
85
86void Geno_f4::setDefaults()
87{
88        excluded_modifiers = F14_MODIFIERS_RARE F14_MODIFIERS_VISUAL;
89}
90
91int Geno_f4::ValidateRecur(f4_Node *geno, int retrycount) const
92{
93        // ! the genotype is geno->child (not geno) !
94        // build from it with repair on
95
96        f4_Cells cells(geno->child, true);
97        cells.simulate();  //we should simulate?!
98
99        // errors not fixed:
100        if (cells.getErrorCode() == GENOPER_OPFAIL)
101        {
102                if (cells.getErrorPos() >= 0) return 1 + cells.getErrorPos();
103                return GENOPER_OPFAIL;
104        }
105        // errors can be fixed
106        if (cells.getErrorCode() == GENOPER_REPAIR)
107        {
108                cells.repairGeno(geno, 1);
109                // note: geno might have been fixed
110                // check again
111                int res2 = GENOPER_OK;
112                if (retrycount > 0)
113                        res2 = ValidateRecur(geno, retrycount - 1);
114
115                if (res2 == GENOPER_OK) return GENOPER_REPAIR;
116                return res2;
117        }
118        // no errors:
119        return GENOPER_OK;
120}
121
122
123int Geno_f4::validate(char *& geno, const char *genoname)
124{
125        // convert geno to a tree, then try to validate
126        f4_Node root;
127        int res = f4_process(geno, &root);
128        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
129
130        // 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)
131        const int VALIDATE_TRIALS = 20;
132        res = ValidateRecur(&root, VALIDATE_TRIALS);
133        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),
134        {
135                geno[0] = 0;
136                root.child->sprintAdj(geno); //make it back to string
137        }
138        return GENOPER_OK;
139}
140
141
142int Geno_f4::checkValidity(const char* geno, const char *genoname)
143{
144        f4_Node root;
145        int res = f4_process(geno, &root);
146        if (res) return res;  // errorpos, >0
147        if (root.childCount() != 1) return 1; // fatal flaw; root must have exactly one child
148        f4_Cells cells(root.child, false);
149        cells.simulate();
150        if (cells.getErrorCode() == GENOPER_OPFAIL || cells.getErrorCode() == GENOPER_REPAIR)
151        {
152                if (cells.getErrorPos() >= 0) return 1 + cells.getErrorPos();
153                else return 1; //error, no known position
154        }
155        else return GENOPER_OK;
156}
157
158
159int Geno_f4::MutateOne(f4_Node *& g, int &method) const
160{
161        // ! the genotype is g->child (not g) !
162
163        // do the mutation
164        // pick a random node
165        f4_Node *node_mutated = g->child->randomNode();
166        //DB( printf("%c\n", node_mutated->name); )
167
168        switch (roulette(prob, F4_COUNT))
169        {
170        case F4_ADD:
171        {
172                // add a node
173                switch (method = roulette(probadd, F4_ADD_COUNT))
174                {
175                case F4_ADD_DIV:
176                {
177                        // add division ('<')
178                        f4_Node *node_mutated_parent = node_mutated->parent;
179                        node_mutated_parent->removeChild(node_mutated);
180                        f4_Node *node_new_div = new f4_Node('<', node_mutated_parent, node_mutated_parent->pos);
181                        node_new_div->addChild(node_mutated);
182                        // new cell is stick or neuron
183                        // "X>" or "N>"
184                        constexpr double STICK_OR_NEURON = 0.5; // hardcoded probability... could be parametrized, but in a general case (unknown fitness goal) 0.5 makes sense?
185                        f4_Node *node_new = NULL; //stick or neuron or neural connection
186                        if (rndDouble(1) < STICK_OR_NEURON)
187                                node_new = new f4_Node('X', node_new_div, node_new_div->pos);
188                        else
189                        {
190                                // make neuron
191                                NeuroClass *rndclass = GenoOperators::getRandomNeuroClass(Model::SHAPETYPE_BALL_AND_STICK);
192                                if (rndclass == NULL) //no active neurons?
193                                {
194                                        node_new = new f4_Node('X', node_new_div, node_new_div->pos);
195                                }
196                                else
197                                {
198                                        f4_Node *node_new_neuron = new f4_Node(rndclass->getName().c_str(), node_new_div, node_new_div->pos);
199                                        node_new_neuron->neuclass = rndclass;
200                                        node_new = node_new_neuron; //can be changed below if all goes well and we add a new connection too
201                                        if (probadd[F4_ADD_CONN] > 0) //user wants to add connections
202                                        {
203                                                if (rndclass->getPreferredInputs() != 0) //neuron also wants connections?
204                                                {
205                                                        int node_new_neuron_index, other_neuron_index;
206                                                        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
207                                                        if (ok) //we can create a new connection
208                                                        {
209                                                                node_new = new f4_Node('[', node_new_neuron, node_new_div->pos);
210                                                                connectionNodeChangeRandom(node_new, node_new_neuron_index, other_neuron_index);
211                                                        }
212                                                }
213                                                else if (rndclass->getPreferredOutput() > 0) //neuron also wants connections?
214                                                {
215                                                        // 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).
216                                                        // The "false" argument in findConnectionNeuronIndexes() below is not suffient, because we also need to access (find) the f4_Node of the other neuron.
217                                                        // A similar logic is implemented in F4_ADD_CONN below, but let's not complicate this F4_ADD_DIV mutation anymore.
218                                                        // 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.).
219                                                        //bool ok = findConnectionNeuronIndexes(g, ... , false, ..., ...);
220                                                }
221                                        }
222                                }
223                        }
224                        new f4_Node('>', node_new, node_new->pos); //adds to node_new
225                        node_mutated->parent = node_new_div;
226                        // now, swap children with 50% chance
227                        if (rndUint(2) == 0)
228                        {
229                                node_mutated_parent = node_new_div->child;
230                                node_new_div->child = node_new_div->child2;
231                                node_new_div->child2 = node_mutated_parent;
232                        }
233                }
234                break;
235                case F4_ADD_CONN:
236                {
237                        // add connection
238
239                        // the probability that a randomly selected node will be a neuron and additionally this neuron will accept inputs is low,
240                        // so we disregard randomly picked node_mutated and build a list of all valid candidate nodes here, then randomly select one from them.
241
242                        vector<f4_Node*> candidate_nodes; //neurons that accept input(s)
243                        for (int i = 0; i < g->count(); i++)
244                        {
245                                f4_Node *node = g->ordNode(i);
246                                f4_Node *node_parent = node->parent;
247                                if (node_parent == NULL || node_parent->neuclass == NULL) continue;
248                                int prefinputs = node_parent->neuclass->getPreferredInputs();
249                                if (prefinputs == -1 ||
250                                        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
251                                        candidate_nodes.push_back(node);
252                        }
253
254                        if (candidate_nodes.size() == 0)
255                                return GENOPER_OPFAIL;
256
257                        node_mutated = candidate_nodes[rndUint((unsigned int)candidate_nodes.size())];
258                        f4_Node *node_mutated_parent = node_mutated->parent;
259
260                        int node_mutated_parent_index, other_neuron_index;
261                        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
262                        if (!ok)
263                                return GENOPER_OPFAIL;
264
265                        node_mutated->parent->removeChild(node_mutated); //this subtree will be reconnected below, as a child to node_new_conn
266                        f4_Node *node_new_conn = new f4_Node('[', node_mutated->parent, node_mutated->parent->pos);
267                        node_new_conn->addChild(node_mutated);
268                        node_mutated->parent = node_new_conn; // node_mutated_parent is the neuron, node_mutated->parent is '['
269                        connectionNodeChangeRandom(node_new_conn, node_mutated_parent_index, other_neuron_index);
270                }
271                break;
272                case F4_ADD_NEUPAR:
273                {
274                        // add neuron modifier
275                        node_mutated->parent->removeChild(node_mutated);
276                        f4_Node *n2 = new f4_Node(':', node_mutated->parent, node_mutated->parent->pos);
277                        nparNodeMakeRandom(n2);
278                        n2->addChild(node_mutated);
279                        node_mutated->parent = n2;
280                }
281                break;
282                case F4_ADD_REP:
283                {
284                        // add repetition ('#')
285                        // repeated code (left child) is the original, right child is empty, count is set to 2
286                        f4_Node *n3 = node_mutated->parent;
287                        n3->removeChild(node_mutated);
288                        f4_Node *n2 = new f4_Node('#', n3, n3->pos);
289                        n2->reps = 2;
290                        n2->addChild(node_mutated);
291                        new f4_Node('>', n2, n2->pos);
292                        node_mutated->parent = n2;
293                }
294                break;
295                case F4_ADD_SIMP:
296                {
297                        // add simple node
298                        int modifier_index = GenoOperators::getRandomChar(all_modifiers, excluded_modifiers.c_str());
299                        if (modifier_index < 0)
300                                return GENOPER_OPFAIL;
301                        node_mutated->parent->removeChild(node_mutated);
302                        // old source: choose a simple node from ADD_SIMPLE_CODES
303                        //f4_Node *n2 = new f4_Node(ADD_SIMPLE_CODES[rndUint(strlen(ADD_SIMPLE_CODES))], node_mutated->parent, node_mutated->parent->pos);
304                        f4_Node *n2 = new f4_Node(all_modifiers[modifier_index], node_mutated->parent, node_mutated->parent->pos);
305                        n2->addChild(node_mutated);
306                        node_mutated->parent = n2;
307                }
308                break;
309                }
310        }
311        break;
312
313        case F4_DEL:
314        {
315                method = F4_ADD_COUNT - 1 + F4_DEL;
316                // delete a node
317                // must pick a node with parent, and at least one child
318                // already picked a node, but repeat may be needed
319                for (int i = 0; i < 10; i++)
320                {
321                        if ((node_mutated->parent != NULL) && (g != node_mutated->parent))
322                                if (node_mutated->child != NULL)
323                                        break;
324                        // try a new one
325                        node_mutated = g->child->randomNode();
326                }
327                if ((node_mutated->parent != NULL) && (g != node_mutated->parent))
328                {
329                        switch (node_mutated->childCount())
330                        {
331                        case 0: break;
332                        case 1:  // one child
333                        {
334                                f4_Node *node_mutated_parent = node_mutated->parent;
335                                node_mutated_parent->removeChild(node_mutated);
336                                if (node_mutated->child != NULL)
337                                {
338                                        node_mutated->child->parent = node_mutated_parent;
339                                        node_mutated_parent->addChild(node_mutated->child);
340                                        node_mutated->child = NULL;
341                                }
342                                if (node_mutated->child2 != NULL)
343                                {
344                                        node_mutated->child2->parent = node_mutated_parent;
345                                        node_mutated_parent->addChild(node_mutated->child2);
346                                        node_mutated->child2 = NULL;
347                                }
348                                // destroy n1
349                                node_mutated->parent = NULL;
350                                delete node_mutated;
351                        }
352                        break;
353
354                        case 2:  // two children
355                        {
356                                // two children
357                                f4_Node *n2 = node_mutated->parent;
358                                n2->removeChild(node_mutated);
359                                // n1 has two children. pick one randomly 50-50, destroy other
360                                if (rndUint(2) == 0)
361                                {
362                                        node_mutated->child->parent = n2;
363                                        n2->addChild(node_mutated->child);
364                                        node_mutated->child = NULL;
365                                        node_mutated->child2->parent = NULL;
366                                }
367                                else
368                                {
369                                        node_mutated->child2->parent = n2;
370                                        n2->addChild(node_mutated->child2);
371                                        node_mutated->child2 = NULL;
372                                        node_mutated->child->parent = NULL;
373                                }
374                                // destroy n1
375                                node_mutated->parent = NULL;
376                                delete node_mutated;
377                        }
378                        break;
379                        }
380                }
381                else return GENOPER_OPFAIL;
382        }
383        break;
384        case F4_MOD:
385        {
386                method = F4_ADD_COUNT - 1 + F4_MOD;
387                // change a node
388                // the only nodes that are modifiable are F4_MUT_CHANGE_CODES
389                // try to get a modifiable node
390                // already picked a node, but repeat may be needed
391                int i = 0;
392                while (1)
393                {
394                        if (strchr(F4_MUT_CHANGE_CODES, node_mutated->name[0])) break;
395                        // try a new one
396                        node_mutated = g->child->randomNode();
397                        i++;
398                        if (i >= 20) return GENOPER_OPFAIL;
399                }
400                switch (node_mutated->name[0])
401                {
402                case '<':
403                {
404                        // swap children
405                        f4_Node *n2 = node_mutated->child;
406                        node_mutated->child = node_mutated->child2;
407                        node_mutated->child2 = n2;
408                }
409                break;
410                case '[':
411                {
412                        switch (roulette(probmodneu, F4_MODNEU_COUNT))
413                        {
414                        case F4_MODNEU_CONN:
415                        {
416                                f4_Node *neuron = node_mutated; //we start in '[' node and follow up parents until we find the neuron with these connections
417                                while (neuron != NULL && neuron->neuclass == NULL) neuron = neuron->parent;
418                                if (neuron == NULL)
419                                        return GENOPER_OPFAIL; //did not find a neuron on the way up tree
420
421
422                                int neuron_index, other_neuron_index;
423                                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
424                                if (!ok)
425                                        return GENOPER_OPFAIL;
426
427                                connectionNodeChangeRandom(node_mutated, neuron_index, other_neuron_index);
428                                break;
429                        }
430                        case F4_MODNEU_WEIGHT:
431                                node_mutated->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(node_mutated->conn_weight);
432                                break;
433                        }
434                }
435                break;
436
437                case '#':
438                {
439                        repeatNodeChangeRandom(node_mutated);
440                }
441                break;
442                }
443        }
444        break;
445
446        default: //no mutations allowed?
447                return GENOPER_OPFAIL;
448        }
449        return GENOPER_OK;
450}
451
452// find all neurons and the needle
453vector<NeuroClass*> Geno_f4::findAllNeuronsAndNode(f4_Node * const & g, f4_Node* const &needle_neuron, int &found_index)
454{
455        found_index = -1; // not found (for example, needle_neuron is not a neuroclass node or not added to the "g" tree)
456        vector<NeuroClass*> neulist;
457        for (int i = 0; i < g->count(); i++)
458        {
459                f4_Node *node = g->ordNode(i);
460                if (node->neuclass != NULL)
461                {
462                        neulist.push_back(node->neuclass);
463                        if (node == needle_neuron)
464                                found_index = int(neulist.size()) - 1;
465                }
466        }
467        return neulist;
468}
469
470bool Geno_f4::findConnectionNeuronIndexes(f4_Node * const &g, f4_Node *neuron, bool other_has_output, int &neuron_index, int &other_neuron_index)
471{
472        vector<NeuroClass*> neulist = findAllNeuronsAndNode(g, neuron, neuron_index);
473        if (neuron_index == -1)
474                return false;
475
476        other_neuron_index = other_has_output ?
477                GenoOperators::getRandomNeuroClassWithOutput(neulist) //find an existing neuron that provides an output
478                :
479                GenoOperators::getRandomNeuroClassWithInput(neulist); //find an existing neuron that accepts input(s)
480        return other_neuron_index >= 0;
481}
482
483// change a [ node
484void Geno_f4::connectionNodeChangeRandom(f4_Node *nn, int nn_index, int other_index) const
485{
486        // relative input connection to some existing neuron
487        nn->conn_from = nn_index - other_index;
488        //nn->conn_from = (int)(4.0f * (rndDouble(1) - 0.5)); //in very old times - did not care about neuron input/output preferences
489
490        nn->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(nn->conn_weight);
491}
492
493
494// make a random : node
495void Geno_f4::nparNodeMakeRandom(f4_Node *nn) const
496{
497        unsigned int prop = rndUint(3); //random neuron property
498        nn->prop_symbol = "!=/"[prop];
499        nn->prop_increase = rndUint(2) == 1;
500}
501
502// change a repeat # node
503void Geno_f4::repeatNodeChangeRandom(f4_Node *nn) const
504{
505        if (rndDouble(1) < 0.5) nn->reps++; else nn->reps--; // change count
506        if (nn->reps < 1) nn->reps = 1;
507        if (nn->reps > mut_max_rep) nn->reps = mut_max_rep;
508}
509
510
511int Geno_f4::MutateOneValid(f4_Node *& g, int &method) const
512// mutate one, until a valid genotype is obtained
513{
514        // ! the genotype is g->child (not g) !
515        int i, res;
516        f4_Node *gcopy = NULL;
517        const int TRY_MUTATE = 20;
518        // try this at most TRY_MUTATE times: copy, mutate, then validate
519        for (i = 0; i < TRY_MUTATE; i++)
520        {
521                gcopy = g->duplicate();
522
523                res = MutateOne(gcopy, method);
524
525                if (GENOPER_OK != res)
526                {
527                        // mutation failed, try again
528                        delete gcopy;
529                        continue;  // for
530                }
531                // try to validate it
532                res = ValidateRecur(gcopy, 10);
533                // accept if it is OK, or was repaired
534                if (GENOPER_OK == res)
535                        //(GENOPER_REPAIR == res)
536                {
537                        // destroy the original one
538                        g->destroy();
539                        // make it the new one
540                        *g = *gcopy;
541                        gcopy->child = NULL;
542                        gcopy->child2 = NULL;
543                        delete gcopy;
544                        res = GENOPER_OK;
545                        goto retm1v;
546                }
547                delete gcopy;
548        }
549        // attempts failed
550        res = GENOPER_OPFAIL;
551retm1v:
552        return res;
553}
554
555
556int Geno_f4::mutate(char *& g, float & chg, int &method)
557{
558        f4_Node *root = new f4_Node;
559        if (f4_process(g, root) || root->childCount() != 1)
560        {
561                delete root;
562                return GENOPER_OPFAIL;
563        } // could not convert or bad: fail
564        // mutate one node, set chg as this percent
565        chg = 1.0 / float(root->child->count());
566        if (MutateOneValid(root, method) != GENOPER_OK)
567        {
568                delete root;
569                return GENOPER_OPFAIL;
570        }
571        // OK, convert back to string
572        g[0] = 0;
573        root->child->sprintAdj(g);
574        delete root;
575        return GENOPER_OK;
576}
577
578
579/*
580int Geno_f4::MutateMany(char *& g, float & chg)
581// check if original is valid, then
582// make a number of mutations
583{
584int res, n, i;
585int totNodes = 0;
586int maxToMut = 0;
587
588// convert to tree
589f4_Node *root;
590root = new f4_Node();
591res = f4_processrec(g, 0, root);
592if (res) {
593// could not convert, fail
594goto retm;
595}
596if (1 != root->childCount()) {
597res = GENOPER_OPFAIL;
598goto retm;
599}
600
601// check if original is valid
602res = ValidateRec( root, 20 );
603// might have been repaired!
604if (GENOPER_REPAIR==res) {
605res = GENOPER_OK;
606}
607if (GENOPER_OK != res) {
608goto retm;
609}
610
611// decide number of nodes to mutate
612// decide maximum number of nodes to mutate: 0.25*nodes, min 2
613totNodes = root->child->count();
614maxToMut = (int)( 0.25f * totNodes);
615if (maxToMut<2) maxToMut=2;
616if (maxToMut>totNodes) maxToMut=totNodes;
617
618// decide number of nodes to mutate
619n = (int)( 0.5 + rndDouble(1) * maxToMut );
620if (n<1) n=1;
621if (n>totNodes) n=totNodes;
622// set chg as this percent
623chg = ((float)n) / ((float)totNodes);
624for (i=0; i<n; i++)
625{
626res = MutateOneValid(root);
627if (GENOPER_OK != res)
628{
629res = GENOPER_OPFAIL;
630goto retm;
631}
632}
633// OK, convert back to string
634g[0]=0;
635root->child->sprintAdj(g);
636retm:
637delete root;
638return res;
639}
640*/
641
642
643int Geno_f4::CrossOverOne(f4_Node *g1, f4_Node *g2, float chg) const
644{
645        // ! the genotypes are g1->child and g2->child (not g1 g2) !
646        // single offspring in g1
647        int smin, smax;
648        float size;
649        f4_Node *n1, *n2, *n1p, *n2p;
650
651        // determine desired size
652        size = (1 - chg) * (float)g1->count();
653        smin = (int)(size * 0.9f - 1);
654        smax = (int)(size * 1.1f + 1);
655        // get a random node with desired size
656        n1 = g1->child->randomNodeWithSize(smin, smax);
657
658        // determine desired size
659        size = (1 - chg) * (float)g2->count();
660        smin = (int)(size * 0.9f - 1);
661        smax = (int)(size * 1.1f + 1);
662        // get a random node with desired size
663        n2 = g2->child->randomNodeWithSize(smin, smax);
664
665        // exchange the two nodes:
666        n1p = n1->parent;
667        n2p = n2->parent;
668        n1p->removeChild(n1);
669        n1p->addChild(n2);
670        n2p->removeChild(n2);
671        n2p->addChild(n1);
672        n1->parent = n2p;
673        n2->parent = n1p;
674
675        return GENOPER_OK;
676}
677
678int Geno_f4::crossOver(char *&g1, char *&g2, float &chg1, float &chg2)
679{
680        f4_Node root1, root2, *copy1, *copy2;
681
682        // convert genotype strings into tree structures
683        if (f4_process(g1, &root1) || (root1.childCount() != 1)) return GENOPER_OPFAIL;
684        if (f4_process(g2, &root2) || (root2.childCount() != 1)) return GENOPER_OPFAIL;
685
686        // decide amounts of crossover, 0.1-0.9
687        chg1 = 0.1 + rndDouble(0.8);
688        chg2 = 0.1 + rndDouble(0.8);
689
690        copy1 = root1.duplicate();
691        if (CrossOverOne(copy1, &root2, chg1) != GENOPER_OK) { delete copy1; copy1 = NULL; }
692        copy2 = root2.duplicate();
693        if (CrossOverOne(copy2, &root1, chg2) != GENOPER_OK) { delete copy2; copy2 = NULL; }
694
695        g1[0] = 0;
696        g2[0] = 0;
697        if (copy1) { copy1->child->sprintAdj(g1); delete copy1; }
698        if (copy2) { copy2->child->sprintAdj(g2); delete copy2; }
699        if (g1[0] || g2[0]) return GENOPER_OK; else return GENOPER_OPFAIL;
700}
701
702uint32_t Geno_f4::style(const char *g, int pos)
703{
704        char ch = g[pos];
705
706        // style categories
707#define STYL4CAT_MODIFIC F14_MODIFIERS ","
708#define STYL4CAT_NEUMOD "/!="
709#define STYL4CAT_NEUSPECIAL "|@*"
710#define STYL4CAT_DIGIT "+-0123456789.[]" //'+' is only for adjusting old-style properties "/!="
711#define STYL4CAT_REST ":XN<># "
712
713        if (!isalpha(ch) && !strchr(STYL4CAT_MODIFIC STYL4CAT_NEUMOD STYL4CAT_NEUSPECIAL STYL4CAT_DIGIT STYL4CAT_REST "\t", ch))
714        {
715                return GENSTYLE_CS(0, GENSTYLE_INVALID);
716        }
717        uint32_t style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT); //default, should be changed below
718        if (strchr("X", ch))                     style = GENSTYLE_CS(0, GENSTYLE_BOLD);
719        else if (strchr(":", ch))                style = GENSTYLE_CS(0, GENSTYLE_NONE);
720        else if (strchr("#", ch))                style = GENSTYLE_RGBS(220, 0, 0, GENSTYLE_BOLD);
721        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
722        else if (strchr("N@|*", ch))             style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); //neuroclass
723        else if (strchr("<", ch))                style = GENSTYLE_RGBS(0, 0, 200, GENSTYLE_BOLD);
724        else if (strchr(">", ch))                style = GENSTYLE_RGBS(0, 0, 100, GENSTYLE_NONE);
725        else if (strchr(STYL4CAT_DIGIT, ch))     style = GENSTYLE_CS(GENCOLOR_NUMBER, GENSTYLE_NONE);
726        else if (strchr(STYL4CAT_MODIFIC, ch))   style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE);
727        else if (strchr(STYL4CAT_NEUMOD, ch))    style = GENSTYLE_RGBS(0, 150, 0, GENSTYLE_NONE);
728        if (isalpha(ch))
729        {
730                // allowed neuron formats:
731                //   N:CLASSNAME
732                //   N:@
733                //   N:|
734                // old syntax still supported in coloring, but no longer valid:
735                //   [SENSOR, WEIGHT]
736                //   N@
737                //   N|
738                // ...so must have N: or [ before neuroclass name (or just N, but this is handled above - for N@|* only)
739
740                while (pos > 0)
741                {
742                        pos--;
743                        if (!isalpha(g[pos]))
744                        {
745                                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 ":"
746                                        style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); // neuroclass
747                                //(...) else (...)
748                                //      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 +! -! += -= +/ -/
749                                break;
750                        }
751                }
752        }
753        return style;
754}
Note: See TracBrowser for help on using the repository browser.