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

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