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

Last change on this file since 1304 was 1298, checked in by Maciej Komosinski, 8 months ago

Introduced overloads for rndUint() with size_t and int arguments to avoid numerous type casts in sources

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