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

Last change on this file since 1227 was 1227, checked in by Maciej Komosinski, 12 months ago

Improvements in f4:

  • fixed a bug where newly created cells in a given development step were not counted as in-active-development (overlooked), and if they were the only in-active-development cells, the development of an organism would stop
  • added one extra development step (#ifdef EXTRA_STEP_CELL_DEVELOPMENT) so that cells that became not in-active-development ("halted" or yielding, usually due to waiting for neurons to develop in other cells) would get a chance to continue development (important when we don't want to ignore invalid neuron connections, #ifdef TREAT_BAD_CONNECTIONS_AS_INVALID_GENO)
  • ensured that all connections in a cell are processed (earlier they could be skipped if the development of the cell was "halted" and all cells became not in-active-development)
  • got rid of neuron connection syntax [sensor:weight], now all neuron classes are handled in a uniform way and their [connections] too; the only allowed syntax is [input_index:weight]
  • unified handling of all neuroclasses during parsing, conversion and mutation
  • more correct syntax coloring
  • got rid of general-purpose fields (i1, i2, f1, s1) in class f4_node - now separate fields serve their individual purpose
  • rewritten creating and modifying neuron connections - it is more deliberate to satisfy neuron input/output preferences
  • some invalid neuron connections make a genotype invalid (previously invalid neuron connections were ignored and the genotype was considered valid)
  • added (surprisingly missing) simple debug printout functions to see the f4_Node tree structure and the developing f4_Cells
  • more informative variable and constant names
  • improved performance
  • Property svn:eol-style set to native
File size: 26.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 support for new API for neuron types and their properties
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 (having valid genotypes with a lot of "junk code") in this representation looks like a good idea.
14//
15// Note: symbols after the last > are ignored, for example /*4*/<X>N:N>N:N[2:-0.5]XXXwhatever but since they are not parsed into the f4_Node tree, they will be lost after any mutation.
16//
17// TODO the behavior of neuron input indexes during mutation seems badly implemented (see also TREAT_BAD_CONNECTIONS_AS_ERRORS). 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 '['.
18// TODO add simplifying sequences of modifiers (so capital and small letter cancel out, like in f1) - but seems like each single modifier is a separate f4_Node? and perhaps we don't want to use the repair mechanism for this... maybe mutations, when they add/modify/remove a modifier node, should be "cleaning" the tree by removing nodes when they encounter contradictory modifiers on the same subpath?
19// TODO add support for properties of (any class of) neurons - not just sigmoid/force/intertia (':' syntax) for N
20// TODO add mapping genotype character ranges for neural [connections]
21
22
23#include "f4_oper.h"
24#include <frams/util/sstring.h>
25#include <common/log.h>
26
27#include <stdio.h>
28#include <stdlib.h>
29#include "common/nonstd_math.h"
30#include <string.h>
31
32
33const char *Geno_f4::all_modifiers = F14_MODIFIERS ","; //comma in f4 is handled the same way (simple node, F4_ADD_SIMP) as modifiers
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 50", FIELD(prob[F4_ADD]), "Mutation: probability of adding a node", },
44        { "f4_mut_add_div", 0, 0, "- add division", "f 0 100 20", 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 15", 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 5", 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 10", 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 50", 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 20", FIELD(prob[F4_DEL]), "Mutation: probability of deleting a node", },
51
52        { "f4_mut_mod", 0, 0, "Modify node", "f 0 100 30", FIELD(prob[F4_MOD]), "Mutation: probability of changing a node", },
53        { "f4_mut_modneu_conn", 0, 0, "- neuron input: modify source", "f 0 100 60", 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 40", 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_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 ")", },
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        excluded_modifiers = F14_MODIFIERS_RARE F14_MODIFIERS_VISUAL;
86}
87
88int Geno_f4::ValidateRec(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, 1);
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 = ValidateRec(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 tree, then try to validate 20 times
123        f4_Node root;
124        if (f4_processrec(geno, 0, &root) || root.childCount() != 1) return GENOPER_OK; // cannot repair
125        if (ValidateRec(&root, 20) == GENOPER_REPAIR) // if repaired, make it back to string
126        {
127                geno[0] = 0;
128                root.child->sprintAdj(geno);
129        }
130        return GENOPER_OK;
131}
132
133
134int Geno_f4::checkValidity(const char* geno, const char *genoname)
135{
136        f4_Node root;
137        int res = f4_processrec(geno, 0, &root);
138        if (res) return res;  // errorpos, >0
139        if (root.childCount() != 1) return 1; //earlier: GENOPER_OPFAIL
140        f4_Cells cells(root.child, 0);
141        cells.simulate();
142        if (cells.getErrorCode() == GENOPER_OPFAIL || cells.getErrorCode() == GENOPER_REPAIR)
143        {
144                if (cells.getErrorPos() >= 0) return 1 + cells.getErrorPos();
145                else return 1; //earlier: GENOPER_OPFAIL;
146        }
147        else return GENOPER_OK;
148}
149
150
151int Geno_f4::MutateOne(f4_Node *& g, int &method) const
152{
153        // ! the genotype is g->child (not g) !
154
155        // do the mutation
156        // pick a random node
157        f4_Node *node_mutated = g->child->randomNode();
158        //DB( printf("%c\n", node_mutated->name); )
159
160        switch (roulette(prob, F4_COUNT))
161        {
162        case F4_ADD:
163        {
164                // add a node
165                switch (method = roulette(probadd, F4_ADD_COUNT))
166                {
167                case F4_ADD_DIV:
168                {
169                        // add division ('<')
170                        f4_Node *node_mutated_parent = node_mutated->parent;
171                        node_mutated_parent->removeChild(node_mutated);
172                        f4_Node *node_new_div = new f4_Node('<', node_mutated_parent, node_mutated_parent->pos);
173                        node_new_div->addChild(node_mutated);
174                        // new cell is stick or neuron
175                        // "X>" or "N>"
176                        constexpr double STICK_OR_NEURON = 0.5; // hardcoded probability... could be parametrized, but in a general case (unknown fitness goal) 0.5 makes sense?
177                        f4_Node *node_new = NULL; //stick or neuron or neural connection
178                        if (rndDouble(1) < STICK_OR_NEURON)
179                                node_new = new f4_Node('X', node_new_div, node_new_div->pos);
180                        else
181                        {
182                                // make neuron
183                                NeuroClass *rndclass = GenoOperators::getRandomNeuroClass(Model::SHAPETYPE_BALL_AND_STICK);
184                                if (rndclass == NULL) //no active neurons?
185                                {
186                                        node_new = new f4_Node('X', node_new_div, node_new_div->pos);
187                                }
188                                else
189                                {
190                                        f4_Node *node_new_neuron = new f4_Node(rndclass->getName().c_str(), node_new_div, node_new_div->pos);
191                                        node_new_neuron->neuclass = rndclass;
192                                        node_new = node_new_neuron; //can be changed below if all goes well and we add a new connection too
193                                        if (probadd[F4_ADD_CONN] > 0) //user wants to add connections
194                                        {
195                                                if (rndclass->getPreferredInputs() != 0) //neuron also wants connections?
196                                                {
197                                                        int node_new_neuron_index, other_neuron_index;
198                                                        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
199                                                        if (ok) //we can create a new connection
200                                                        {
201                                                                node_new = new f4_Node('[', node_new_neuron, node_new_div->pos);
202                                                                connectionNodeChangeRandom(node_new, node_new_neuron_index, other_neuron_index);
203                                                        }
204                                                }
205                                                else if (rndclass->getPreferredOutput() > 0) //neuron also wants connections?
206                                                {
207                                                        // 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).
208                                                        // The "false" argument in findConnectionNeuronIndexes() below is not suffient, because we also need to access (find) the f4_Node of the other neuron.
209                                                        // A similar logic is implemented in F4_ADD_CONN below, but let's not complicate this F4_ADD_DIV mutation anymore.
210                                                        // A 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.).
211                                                        //bool ok = findConnectionNeuronIndexes(g, ... , false, ..., ...);
212                                                }
213                                        }
214                                }
215                        }
216                        new f4_Node('>', node_new, node_new->pos); //adds to node_new
217                        node_mutated->parent = node_new_div;
218                        // now, swap children with 50% chance
219                        if (rndUint(2) == 0)
220                        {
221                                node_mutated_parent = node_new_div->child;
222                                node_new_div->child = node_new_div->child2;
223                                node_new_div->child2 = node_mutated_parent;
224                        }
225                }
226                break;
227                case F4_ADD_CONN:
228                {
229                        // add connection
230
231                        // the probability that a randomly selected node will be a neuron and additionally this neuron will accept inputs is low,
232                        // so we disregard randomly picked node_mutated and build a list of all valid candidate nodes here, then select a random one from them.
233
234                        vector<f4_Node*> candidate_nodes; //neurons that accept input(s)
235                        for (int i = 0; i < g->count(); i++)
236                        {
237                                f4_Node *node = g->ordNode(i);
238                                f4_Node *node_parent = node->parent;
239                                if (node_parent == NULL || node_parent->neuclass == NULL) continue;
240                                int prefinputs = node_parent->neuclass->getPreferredInputs();
241                                if (prefinputs == -1 ||
242                                        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
243                                        candidate_nodes.push_back(node);
244                        }
245
246                        if (candidate_nodes.size() == 0)
247                                return GENOPER_OPFAIL;
248
249                        node_mutated = candidate_nodes[rndUint((unsigned int)candidate_nodes.size())];
250                        f4_Node *node_mutated_parent = node_mutated->parent;
251
252                        int node_mutated_parent_index, other_neuron_index;
253                        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
254                        if (!ok)
255                                return GENOPER_OPFAIL;
256
257                        node_mutated->parent->removeChild(node_mutated); //this subtree will be reconnected below, as a child to node_new_conn
258                        f4_Node *node_new_conn = new f4_Node('[', node_mutated->parent, node_mutated->parent->pos);
259                        node_new_conn->addChild(node_mutated);
260                        node_mutated->parent = node_new_conn; // node_mutated_parent is the neuron, node_mutated->parent is '['
261                        connectionNodeChangeRandom(node_new_conn, node_mutated_parent_index, other_neuron_index);
262                }
263                break;
264                case F4_ADD_NEUPAR:
265                {
266                        // add neuron modifier
267                        node_mutated->parent->removeChild(node_mutated);
268                        f4_Node *n2 = new f4_Node(':', node_mutated->parent, node_mutated->parent->pos);
269                        nparNodeMakeRandom(n2);
270                        n2->addChild(node_mutated);
271                        node_mutated->parent = n2;
272                }
273                break;
274                case F4_ADD_REP:
275                {
276                        // add repetition ('#')
277                        // repeated code (left child) is the original, right child is empty, count is set to 2
278                        f4_Node *n3 = node_mutated->parent;
279                        n3->removeChild(node_mutated);
280                        f4_Node *n2 = new f4_Node('#', n3, n3->pos);
281                        n2->reps = 2;
282                        n2->addChild(node_mutated);
283                        new f4_Node('>', n2, n2->pos);
284                        node_mutated->parent = n2;
285                }
286                break;
287                case F4_ADD_SIMP:
288                {
289                        // add simple node
290                        // choose a simple node from ADD_SIMPLE_CODES
291                        node_mutated->parent->removeChild(node_mutated);
292                        //f4_Node *n2 = new f4_Node(ADD_SIMPLE_CODES[rndUint(strlen(ADD_SIMPLE_CODES))], n1->parent, n1->parent->pos);
293                        int modifierid = GenoOperators::getRandomChar(all_modifiers, excluded_modifiers.c_str());
294                        f4_Node *n2 = new f4_Node(all_modifiers[modifierid], node_mutated->parent, node_mutated->parent->pos);
295                        n2->addChild(node_mutated);
296                        node_mutated->parent = n2;
297                }
298                break;
299                }
300        }
301        break;
302
303        case F4_DEL:
304        {
305                method = F4_ADD_COUNT - 1 + F4_DEL;
306                // delete a node
307                // must pick a node with parent, and at least one child
308                // already picked a node, but repeat may be needed
309                for (int i = 0; i < 10; i++)
310                {
311                        if ((node_mutated->parent != NULL) && (g != node_mutated->parent))
312                                if (node_mutated->child != NULL)
313                                        break;
314                        // try a new one
315                        node_mutated = g->child->randomNode();
316                }
317                if ((node_mutated->parent != NULL) && (g != node_mutated->parent))
318                {
319                        switch (node_mutated->childCount())
320                        {
321                        case 0: break;
322                        case 1:  // one child
323                        {
324                                f4_Node *node_mutated_parent = node_mutated->parent;
325                                node_mutated_parent->removeChild(node_mutated);
326                                if (node_mutated->child != NULL)
327                                {
328                                        node_mutated->child->parent = node_mutated_parent;
329                                        node_mutated_parent->addChild(node_mutated->child);
330                                        node_mutated->child = NULL;
331                                }
332                                if (node_mutated->child2 != NULL)
333                                {
334                                        node_mutated->child2->parent = node_mutated_parent;
335                                        node_mutated_parent->addChild(node_mutated->child2);
336                                        node_mutated->child2 = NULL;
337                                }
338                                // destroy n1
339                                node_mutated->parent = NULL;
340                                delete node_mutated;
341                        }
342                        break;
343
344                        case 2:  // two children
345                        {
346                                // two children
347                                f4_Node *n2 = node_mutated->parent;
348                                n2->removeChild(node_mutated);
349                                // n1 has two children. pick one randomly 50-50, destroy other
350                                if (rndUint(2) == 0)
351                                {
352                                        node_mutated->child->parent = n2;
353                                        n2->addChild(node_mutated->child);
354                                        node_mutated->child = NULL;
355                                        node_mutated->child2->parent = NULL;
356                                }
357                                else
358                                {
359                                        node_mutated->child2->parent = n2;
360                                        n2->addChild(node_mutated->child2);
361                                        node_mutated->child2 = NULL;
362                                        node_mutated->child->parent = NULL;
363                                }
364                                // destroy n1
365                                node_mutated->parent = NULL;
366                                delete node_mutated;
367                        }
368                        break;
369                        }
370                }
371                else return GENOPER_OPFAIL;
372        }
373        break;
374        case F4_MOD:
375        {
376                method = F4_ADD_COUNT - 1 + F4_MOD;
377                // change a node
378                // the only nodes that are modifiable are F4_MUT_CHANGE_CODES
379                // try to get a modifiable node
380                // already picked a node, but repeat may be needed
381                int i = 0;
382                while (1)
383                {
384                        if (strchr(F4_MUT_CHANGE_CODES, node_mutated->name[0])) break;
385                        // try a new one
386                        node_mutated = g->child->randomNode();
387                        i++;
388                        if (i >= 20) return GENOPER_OPFAIL;
389                }
390                switch (node_mutated->name[0])
391                {
392                case '<':
393                {
394                        // swap children
395                        f4_Node *n2 = node_mutated->child;
396                        node_mutated->child = node_mutated->child2;
397                        node_mutated->child2 = n2;
398                }
399                break;
400                case '[':
401                {
402                        switch (roulette(probmodneu, F4_MODNEU_COUNT))
403                        {
404                        case F4_MODNEU_CONN:
405                        {
406                                f4_Node *neuron = node_mutated; //we start in '[' node and follow up parents until we find the neuron with these connections
407                                while (neuron != NULL && neuron->neuclass == NULL) neuron = neuron->parent;
408                                if (neuron == NULL)
409                                        return GENOPER_OPFAIL; //did not find a neuron on the way up tree
410
411
412                                int neuron_index, other_neuron_index;
413                                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
414                                if (!ok)
415                                        return GENOPER_OPFAIL;
416
417                                connectionNodeChangeRandom(node_mutated, neuron_index, other_neuron_index);
418                                break;
419                        }
420                        case F4_MODNEU_WEIGHT:
421                                node_mutated->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(node_mutated->conn_weight);
422                                break;
423                        }
424                }
425                break;
426
427                case '#':
428                {
429                        repeatNodeChangeRandom(node_mutated);
430                }
431                break;
432                }
433        }
434        break;
435
436        default: //no mutations allowed?
437                return GENOPER_OPFAIL;
438        }
439        return GENOPER_OK;
440}
441
442// find all neurons and the needle
443vector<NeuroClass*> Geno_f4::findAllNeuronsAndNode(f4_Node * const & g, f4_Node* const &needle_neuron, int &found_index)
444{
445        found_index = -1; // not found (for example, needle_neuron is not a neuroclass node or not added to the "g" tree)
446        vector<NeuroClass*> neulist;
447        for (int i = 0; i < g->count(); i++)
448        {
449                f4_Node *node = g->ordNode(i);
450                if (node->neuclass != NULL)
451                {
452                        neulist.push_back(node->neuclass);
453                        if (node == needle_neuron)
454                                found_index = int(neulist.size()) - 1;
455                }
456        }
457        return neulist;
458}
459
460bool Geno_f4::findConnectionNeuronIndexes(f4_Node * const &g, f4_Node *neuron, bool other_has_output, int &neuron_index, int &other_neuron_index)
461{
462        vector<NeuroClass*> neulist = findAllNeuronsAndNode(g, neuron, neuron_index);
463        if (neuron_index == -1)
464                return false;
465
466        other_neuron_index = other_has_output ?
467                GenoOperators::getRandomNeuroClassWithOutput(neulist) //find an existing neuron that provides an output
468                :
469                GenoOperators::getRandomNeuroClassWithInput(neulist); //find an existing neuron that accepts input(s)
470        return other_neuron_index >= 0;
471}
472
473// change a [ node
474void Geno_f4::connectionNodeChangeRandom(f4_Node *nn, int nn_index, int other_index) const
475{
476        // relative input connection to some existing neuron
477        nn->conn_from = nn_index - other_index;
478        //nn->conn_from = (int)(4.0f * (rndDouble(1) - 0.5f)); //in very old times - did not care about neuron input/output preferences
479
480        nn->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(nn->conn_weight);
481}
482
483
484// make a random : node
485void Geno_f4::nparNodeMakeRandom(f4_Node *nn) const
486{
487        unsigned int prop = rndUint(3); //random neuron property
488        nn->prop_symbol = "!=/"[prop];
489        nn->prop_increase = rndUint(2) == 1;
490}
491
492// change a repeat # node
493void Geno_f4::repeatNodeChangeRandom(f4_Node *nn) const
494{
495        if (rndDouble(1) < 0.5f) nn->reps++; else nn->reps--; // change count
496        if (nn->reps < 1) nn->reps = 1;
497        if (nn->reps > mut_max_rep) nn->reps = mut_max_rep;
498}
499
500
501int Geno_f4::MutateOneValid(f4_Node *& g, int &method) const
502// mutate one, until a valid genotype is obtained
503{
504        // ! the genotype is g->child (not g) !
505        int i, res;
506        f4_Node *gcopy = NULL;
507        const int TRY_MUTATE = 20;
508        // try this at most TRY_MUTATE times: copy, mutate, then validate
509        for (i = 0; i < TRY_MUTATE; i++)
510        {
511                gcopy = g->duplicate();
512
513                res = MutateOne(gcopy, method);
514
515                if (GENOPER_OK != res)
516                {
517                        // mutation failed, try again
518                        delete gcopy;
519                        continue;  // for
520                }
521                // try to validate it
522                res = ValidateRec(gcopy, 10);
523                // accept if it is OK, or was repaired
524                if (GENOPER_OK == res)
525                        //(GENOPER_REPAIR == res)
526                {
527                        // destroy the original one
528                        g->destroy();
529                        // make it the new one
530                        *g = *gcopy;
531                        gcopy->child = NULL;
532                        gcopy->child2 = NULL;
533                        delete gcopy;
534                        res = GENOPER_OK;
535                        goto retm1v;
536                }
537                delete gcopy;
538        }
539        // attempts failed
540        res = GENOPER_OPFAIL;
541retm1v:
542        return res;
543}
544
545
546int Geno_f4::mutate(char *& g, float & chg, int &method)
547{
548        f4_Node *root = new f4_Node;
549        if (f4_processrec(g, 0, root) || root->childCount() != 1)
550        {
551                delete root;
552                return GENOPER_OPFAIL;
553        } // could not convert or bad: fail
554        // mutate one node, set chg as this percent
555        chg = 1.0 / float(root->child->count());
556        if (MutateOneValid(root, method) != GENOPER_OK)
557        {
558                delete root;
559                return GENOPER_OPFAIL;
560        }
561        // OK, convert back to string
562        g[0] = 0;
563        root->child->sprintAdj(g);
564        delete root;
565        return GENOPER_OK;
566}
567
568
569/*
570int Geno_f4::MutateMany(char *& g, float & chg)
571// check if original is valid, then
572// make a number of mutations
573{
574int res, n, i;
575int totNodes = 0;
576int maxToMut = 0;
577
578// convert to tree
579f4_Node *root;
580root = new f4_Node();
581res = f4_processrec(g, 0, root);
582if (res) {
583// could not convert, fail
584goto retm;
585}
586if (1 != root->childCount()) {
587res = GENOPER_OPFAIL;
588goto retm;
589}
590
591// check if original is valid
592res = ValidateRec( root, 20 );
593// might have been repaired!
594if (GENOPER_REPAIR==res) {
595res = GENOPER_OK;
596}
597if (GENOPER_OK != res) {
598goto retm;
599}
600
601// decide number of nodes to mutate
602// decide maximum number of nodes to mutate: 0.25*nodes, min 2
603totNodes = root->child->count();
604maxToMut = (int)( 0.25f * totNodes);
605if (maxToMut<2) maxToMut=2;
606if (maxToMut>totNodes) maxToMut=totNodes;
607
608// decide number of nodes to mutate
609n = (int)( 0.5f + rndDouble(1) * maxToMut );
610if (n<1) n=1;
611if (n>totNodes) n=totNodes;
612// set chg as this percent
613chg = ((float)n) / ((float)totNodes);
614for (i=0; i<n; i++)
615{
616res = MutateOneValid(root);
617if (GENOPER_OK != res)
618{
619res = GENOPER_OPFAIL;
620goto retm;
621}
622}
623// OK, convert back to string
624g[0]=0;
625root->child->sprintAdj(g);
626retm:
627delete root;
628return res;
629}
630*/
631
632
633int Geno_f4::CrossOverOne(f4_Node *g1, f4_Node *g2, float chg) const
634{
635        // ! the genotypes are g1->child and g2->child (not g1 g2) !
636        // single offspring in g1
637        int smin, smax;
638        float size;
639        f4_Node *n1, *n2, *n1p, *n2p;
640
641        // determine desired size
642        size = (1 - chg) * (float)g1->count();
643        smin = (int)(size * 0.9f - 1);
644        smax = (int)(size * 1.1f + 1);
645        // get a random node with desired size
646        n1 = g1->child->randomNodeWithSize(smin, smax);
647
648        // determine desired size
649        size = (1 - chg) * (float)g2->count();
650        smin = (int)(size * 0.9f - 1);
651        smax = (int)(size * 1.1f + 1);
652        // get a random node with desired size
653        n2 = g2->child->randomNodeWithSize(smin, smax);
654
655        // exchange the two nodes:
656        n1p = n1->parent;
657        n2p = n2->parent;
658        n1p->removeChild(n1);
659        n1p->addChild(n2);
660        n2p->removeChild(n2);
661        n2p->addChild(n1);
662        n1->parent = n2p;
663        n2->parent = n1p;
664
665        return GENOPER_OK;
666}
667
668int Geno_f4::crossOver(char *&g1, char *&g2, float &chg1, float &chg2)
669{
670        f4_Node root1, root2, *copy1, *copy2;
671
672        // convert genotype strings into tree structures
673        if (f4_processrec(g1, 0, &root1) || (root1.childCount() != 1)) return GENOPER_OPFAIL;
674        if (f4_processrec(g2, 0, &root2) || (root2.childCount() != 1)) return GENOPER_OPFAIL;
675
676        // decide amounts of crossover, 0.25-0.75
677        // adam: seems 0.1-0.9 -- MacKo
678        chg1 = 0.1 + rndDouble(0.8);
679        chg2 = 0.1 + rndDouble(0.8);
680
681        copy1 = root1.duplicate();
682        if (CrossOverOne(copy1, &root2, chg1) != GENOPER_OK) { delete copy1; copy1 = NULL; }
683        copy2 = root2.duplicate();
684        if (CrossOverOne(copy2, &root1, chg2) != GENOPER_OK) { delete copy2; copy2 = NULL; }
685
686        g1[0] = 0;
687        g2[0] = 0;
688        if (copy1) { copy1->child->sprintAdj(g1); delete copy1; }
689        if (copy2) { copy2->child->sprintAdj(g2); delete copy2; }
690        if (g1[0] || g2[0]) return GENOPER_OK; else return GENOPER_OPFAIL;
691}
692
693uint32_t Geno_f4::style(const char *g, int pos)
694{
695        char ch = g[pos];
696
697        // style categories
698#define STYL4CAT_MODIFIC F14_MODIFIERS ","
699#define STYL4CAT_NEUMOD "/!="
700#define STYL4CAT_NEUSPECIAL "|@*"
701#define STYL4CAT_DIGIT "+-0123456789.[]" //'+' is only for adjusting old-style properties "/!="
702#define STYL4CAT_REST ":XN<># "
703
704        if (!isalpha(ch) && !strchr(STYL4CAT_MODIFIC STYL4CAT_NEUMOD STYL4CAT_NEUSPECIAL STYL4CAT_DIGIT STYL4CAT_REST "\t", ch))
705        {
706                return GENSTYLE_CS(0, GENSTYLE_INVALID);
707        }
708        uint32_t style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT); //default, should be changed below
709        if (strchr("X", ch))                     style = GENSTYLE_CS(0, GENSTYLE_BOLD);
710        else if (strchr(":", ch))                style = GENSTYLE_CS(0, GENSTYLE_NONE);
711        else if (strchr("#", ch))                style = GENSTYLE_RGBS(220, 0, 0, GENSTYLE_BOLD);
712        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
713        else if (strchr("N@|*", ch))             style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); //neuroclass
714        else if (strchr("<", ch))                style = GENSTYLE_RGBS(0, 0, 200, GENSTYLE_BOLD);
715        else if (strchr(">", ch))                style = GENSTYLE_RGBS(0, 0, 100, GENSTYLE_NONE);
716        else if (strchr(STYL4CAT_DIGIT, ch))     style = GENSTYLE_CS(GENCOLOR_NUMBER, GENSTYLE_NONE);
717        else if (strchr(STYL4CAT_MODIFIC, ch))   style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE);
718        else if (strchr(STYL4CAT_NEUMOD, ch))    style = GENSTYLE_RGBS(0, 150, 0, GENSTYLE_NONE);
719        if (isalpha(ch))
720        {
721                // allowed neuron formats:
722                //   N:CLASSNAME
723                //   N:@
724                //   N:|
725                // old syntax still supported in coloring, but no longer valid:
726                //   [SENSOR, WEIGHT]
727                //   N@
728                //   N|
729                // ...so must have N: or [ before neuroclass name (or just N, but this is handled above - for N@|* only)
730
731                while (pos > 0)
732                {
733                        pos--;
734                        if (!isalpha(g[pos]))
735                        {
736                                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 ":"
737                                        style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); // neuroclass
738                                //(...) else (...)
739                                //      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 +! -! += -= +/ -/
740                                break;
741                        }
742                }
743        }
744        return style;
745}
Note: See TracBrowser for help on using the repository browser.