source: cpp/frams/genetics/f4/f4_general.cpp @ 1237

Last change on this file since 1237 was 1237, checked in by Maciej Komosinski, 12 months ago
  • Got rid of a redundant source of information: cells development stops when a cell sets an error code during its development, not when a function returns an error code
  • Fix whitespace repair being potentially overwritten by further repairs
  • Property svn:eol-style set to native
File size: 44.7 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// 2018, Grzegorz Latosinski, added development checkpoints and support for new API for neuron types
7
8#include "f4_general.h"
9#include "../genooperators.h" // for GENOPER_ constants
10#include <common/nonstd_stl.h>
11#include <common/log.h>
12#include <frams/model/model.h> // for min and max attributes
13#include <common/nonstd_math.h>
14
15#ifdef DMALLOC
16#include <dmalloc.h>
17#endif
18
19
20#define BREAK_WHEN_REP_COUNTER_NULL //see comments where it is used
21#define EXTRA_STEP_CELL_DEVELOPMENT //see comments where it is used
22#define TREAT_BAD_CONNECTIONS_AS_INVALID_GENO //see comments where it is used
23
24
25void rolling_dec(double *v)
26{
27        *v -= 0.7853;  // 0.7853981  45 degrees = pi/4 like in f1
28}
29
30void rolling_inc(double *v)
31{
32        *v += 0.7853;  // 0.7853981  45 degrees
33}
34
35
36f4_Cell::f4_Cell(int nnr, f4_Cell *ndad, int nangle, GeneProps newP)
37{
38        nr = nnr;
39        type = CELL_UNDIFF;
40        dadlink = ndad;
41        org = NULL;
42        genot = NULL;
43        gcur = NULL;
44        active = true;
45        repeat.clear();
46        //genoRange.clear(); -- implicit
47
48        anglepos = nangle;
49        commacount = 0;
50        childcount = 0;
51        P = newP;
52        rolling = 0;
53        xrot = 0;
54        zrot = 0;
55        //OM = Orient_1;
56        inertia = 0.8;
57        force = 0.04;
58        sigmo = 2;
59        conns_count = 0;
60
61        // adjust firstend and OM if there is a stick dad
62        if (ndad != NULL)
63        {
64                // make sure it is a stick (and not a stick f4_Cell!)
65                if (ndad->type == CELL_STICK)
66                {
67                        //firstend = ndad->lastend;
68                        //OM = ndad->OM;
69                        ndad->childcount++;
70                }
71                if (ndad->type == CELL_NEURON)
72                {
73                        inertia = ndad->inertia;
74                        force = ndad->force;
75                        sigmo = ndad->sigmo;
76                }
77        }
78        // adjust lastend
79        //lastend = firstend + ((Orient)OM * (Pt3D(1,0,0) * P.len));
80        mz = 1;
81}
82
83
84f4_Cell::f4_Cell(f4_Cells *nO, int nnr, f4_Node *ngeno, f4_Node *ngcur, f4_Cell *ndad, int nangle, GeneProps newP)
85{
86        nr = nnr;
87        type = CELL_UNDIFF;
88        dadlink = ndad;
89        org = nO;
90        genot = ngeno;
91        gcur = ngcur;
92        active = true;
93        repeat.clear();
94        //genoRange.clear(); -- implicit
95        // preserve geno range of parent cell
96        if (NULL != ndad)
97                genoRange.add(ndad->genoRange);
98
99        anglepos = nangle;
100        commacount = 0;
101        childcount = 0;
102        P = newP;
103        rolling = 0;
104        xrot = 0;
105        zrot = 0;
106        //OM = Orient_1;
107        inertia = 0.8;
108        force = 0.04;
109        sigmo = 2;
110        conns_count = 0;
111
112        // adjust firstend and OM if there is a stick dad
113        if (ndad != NULL)
114        {
115                // make sure it is a stick (and not a stick f4_Cell!)
116                if (ndad->type == CELL_STICK)
117                {
118                        //firstend = ndad->lastend;
119                        //OM = ndad->OM;
120                        ndad->childcount++;
121                }
122                if (ndad->type == CELL_NEURON)
123                {
124                        inertia = ndad->inertia;
125                        force = ndad->force;
126                        sigmo = ndad->sigmo;
127                }
128        }
129        // adjust lastend
130        //lastend = firstend + ((Orient)OM * (Pt3D(1,0,0) * P.len));
131        mz = 1;
132}
133
134
135f4_Cell::~f4_Cell()
136{
137        // remove connections
138        if (conns_count)
139        {
140                int i;
141                for (i = conns_count - 1; i >= 0; i--)
142                        delete conns[i];
143                conns_count = 0;
144        }
145}
146
147void f4_Cell::oneStep()
148{
149        while (gcur != NULL)
150        {
151                //DB( printf("  %d (%d) executing '%c' %d\n", name, type, gcur->name, gcur->pos); )
152                // currently this is the last one processed
153                // the current genotype code is processed
154                //genoRange.add(gcur->pos,gcur->pos+gcur->name.length()-1);
155
156                // To detect what genes are valid neuroclass names, but do NOT have is_neuroclass==true
157                // (just as a curiosity to ensure we properly distinguish between, for example, the "G" neuron and the "G" modifier):
158                //char *TMP = (char*)gcur->name.c_str();
159                //if (gcur->is_neuroclass==false && GenoOperators::parseNeuroClass(TMP, ModelEnum::SHAPETYPE_BALL_AND_STICK))
160                //      printf("Could be a valid neuroclass, but is_neuroclass==false: %s\n", gcur->name.c_str());
161
162                if (gcur->neuclass == NULL) //not a neuron
163                {
164                        if (gcur->name.length() > 1)
165                                logPrintf("f4_Cell", "oneStep", LOG_WARN, "Multiple-character code that is not a neuron class name: '%s'", gcur->name.c_str()); //let's see an example of such a code...
166
167                        genoRange.add(gcur->pos, gcur->pos);
168                        char name = gcur->name[0];
169                        switch (name)
170                        {
171                        case '<':
172                        {
173                                // cell division!
174                                //DB( printf("  div! %d\n", name); )
175
176                                // error: sticks cannot divide
177                                if (type == CELL_STICK)
178                                {
179                                        // cannot fix
180                                        org->setError(gcur->pos);
181                                        return;  // error code set -> stop further cells development
182                                }
183
184                                // undiff divides
185                                if (type == CELL_UNDIFF)
186                                {
187                                        // commacount is set only when daughter turns into X
188                                        // daughter cell
189                                        // adjust new len
190                                        GeneProps newP = P;
191                                        newP.propagateAlong(false);
192                                        f4_Cell *tmp = new f4_Cell(org, org->cell_count, genot, gcur->child2, this, commacount, newP);
193                                        tmp->repeat = repeat;
194                                        repeat.clear();
195                                        org->addCell(tmp);
196                                }
197                                // a neuron divides: create a new, duplicate connections
198                                if (type == CELL_NEURON)
199                                {
200                                        // daughter cell
201                                        f4_Cell *tmp = new f4_Cell(org, org->cell_count, genot, gcur->child2,
202                                                // has the same dadlink
203                                                this->dadlink, commacount, P);
204                                        tmp->repeat = repeat;
205                                        repeat.clear();
206                                        // it is a neuron from start
207                                        tmp->type = CELL_NEURON;
208                                        // it has the same type as the parent neuron
209                                        tmp->neuclass = neuclass;
210                                        // duplicate connections
211                                        f4_CellConn *conn;
212                                        for (int i = 0; i < conns_count; i++)
213                                        {
214                                                conn = conns[i];
215                                                tmp->addConnection(conn->from, conn->weight);
216                                        }
217                                        org->addCell(tmp);
218                                }
219                                // adjustments for this cell
220                                gcur = gcur->child;
221                                return;  // error code not set -> halt this development and yield to other cells to develop
222                        }
223                        case '>':
224                        {
225                                // finish
226                                // see if there is a repeat count
227                                if (repeat.top > 0)
228                                { // there is a repeat counter
229                                        if (!repeat.first()->isNull())
230                                        { // repeat counter is not null
231                                                repeat.first()->dec();
232                                                if (repeat.first()->count > 0)
233                                                {
234                                                        // return to repeat
235                                                        gcur = repeat.first()->node->child;
236                                                }
237                                                else
238                                                {
239                                                        // continue
240                                                        gcur = repeat.first()->node->child2;
241                                                        repeat.pop();
242                                                }
243                                                break;
244                                        }
245                                        else
246                                        {
247                                                repeat.pop();
248                                                // MacKo 2023-04: originally, there was no "break" nor "return" here (hence [[fallthrough]]; needed below for modern compilers) - not sure if this was intentional or overlooking.
249                                                // This case can be tested with "#0" in the genotype. Anyway, there seems to be no difference in outcomes with and without "break".
250                                                // However, falling through [[fallthrough]] below for count==0 causes performing repeat.push(repeat_ptr(gcur, 0)) while the very reason
251                                                // we are here is that repeat count==0 (one of the conditions for isNull()), so I opted to add "break", but marked this tentative decision using #define.
252                                                // The ultimate informed decision would require understanding all the logic and testing all the edge cases.
253#ifdef BREAK_WHEN_REP_COUNTER_NULL
254                                                break;
255#endif
256                                        }
257                                }
258                                else
259                                {
260                                        // error: still undiff
261                                        if (type == CELL_UNDIFF)
262                                        {
263                                                // fix it: insert an 'X'
264                                                f4_Node *insertnode = new f4_Node("X", NULL, gcur->pos);
265                                                if (org->setRepairInsert(gcur->pos, gcur, insertnode)) // not in repair mode, release
266                                                        delete insertnode;
267                                                return;  // error code set -> stop further cells development
268                                        }
269                                        repeat.clear();
270                                        active = false;  // stop
271                                        // eat up rest
272                                        int remaining_nodes = gcur->count() - 1;
273                                        if (remaining_nodes > 0)
274                                                logPrintf("f4_Cell", "oneStep", LOG_WARN, "Ignoring junk genetic code: %d node(s) at position %d", remaining_nodes, gcur->child->pos); //let's see an example of such a genotype...
275                                        gcur = NULL;
276                                        return;  // done development
277                                }
278                        }
279#ifndef BREAK_WHEN_REP_COUNTER_NULL
280                        [[fallthrough]];
281#endif
282                        case '#':
283                        {
284                                // repetition marker
285                                if (repeat.top >= repeat_stack::stackSize)
286                                {
287                                        // repeat pointer stack is full, cannot remember this one.
288                                        // fix: delete it
289                                        org->setRepairRemove(gcur->pos, gcur);
290                                        return;  // error code set -> stop further cells development
291                                }
292                                repeat.push(repeat_ptr(gcur, gcur->reps));
293                                gcur = gcur->child;
294                                break;
295                        }
296                        case ',':
297                        {
298                                commacount++;
299                                gcur = gcur->child;
300                                break;
301                        }
302                        case 'r':
303                        case 'R':
304                        {
305                                // error: if neuron
306                                if (type == CELL_NEURON)
307                                {
308                                        // fix: delete it
309                                        org->setRepairRemove(gcur->pos, gcur);
310                                        return;  // error code set -> stop further cells development
311                                }
312                                switch (name)
313                                {
314                                case 'r':   rolling_dec(&rolling); break;
315                                case 'R':   rolling_inc(&rolling); break;
316                                }
317                                gcur = gcur->child;
318                                break;
319                        }
320                        case 'l':  case 'L':
321                        case 'c':  case 'C':
322                        case 'q':  case 'Q':
323                        case 'a':  case 'A':
324                        case 'i':  case 'I':
325                        case 's':  case 'S':
326                        case 'm':  case 'M':
327                        case 'f':  case 'F':
328                        case 'w':  case 'W':
329                        case 'e':  case 'E':
330                        case 'd':  case 'D':
331                        case 'g':  case 'G':
332                        case 'b':  case 'B':
333                        case 'h':  case 'H':
334                        {
335                                // error: if neuron
336                                if (type == CELL_NEURON) //some neurons have the same single-letter names as modifiers (for example G,S,D), but they are supposed to have is_neuroclass==true so they should indeed not be handled here
337                                {//however, what we see here is actually modifiers such as IdqEbWL (so not valid neuroclasses) that occurred within an already differentiated cell of type==CELL_NEURON.
338                                        //printf("Handled as a modifier, but type==CELL_NEURON: '%c'\n", name);
339                                        // fix: delete it
340                                        org->setRepairRemove(gcur->pos, gcur);
341                                        return;  // error code set -> stop further cells development
342                                }
343                                P.executeModifier(name);
344                                gcur = gcur->child;
345                                break;
346                        }
347                        case 'X':
348                        {
349                                // turn undiff. cell into a stick
350                                // error: already differentiated
351                                if (type != CELL_UNDIFF)
352                                {
353                                        // fix: delete this node
354                                        org->setRepairRemove(gcur->pos, gcur);
355                                        return;  // error code set -> stop further cells development
356                                }
357                                type = CELL_STICK;
358                                // fix dad commacount and own anglepos
359                                if (dadlink != NULL)
360                                {
361                                        dadlink->commacount++;
362                                        anglepos = dadlink->commacount;
363                                }
364                                // change of type halts developments, see comment at 'neuclasshandler' below
365                                gcur = gcur->child;
366                                return;  // error code not set -> halt this development and yield to other cells to develop
367                        }
368                        case '[':
369                        {
370                                // connection to neuron
371                                // error: not a neuron
372                                if (type != CELL_NEURON)
373                                {
374                                        // fix: delete it
375                                        org->setRepairRemove(gcur->pos, gcur);
376                                        return;  // error code set -> stop further cells development
377                                }
378                                // input [%d:%g]
379                                int relfrom = gcur->conn_from;
380                                double weight = gcur->conn_weight;
381                                f4_Cell *neu_from = NULL;
382
383                                // input from other neuron
384                                // find neuron at relative i
385                                // find own index
386                                int this_index = 0, neu_counter = 0;
387                                for (int i = 0; i < org->cell_count; i++)
388                                {
389                                        if (org->C[i]->type == CELL_NEURON) neu_counter++;
390                                        if (org->C[i] == this) { this_index = neu_counter - 1; break; }
391                                }
392                                // find index of incoming
393                                int from_index = this_index + relfrom;
394
395                                if (from_index < 0) goto wait_conn;
396                                if (from_index >= org->cell_count) goto wait_conn;
397
398                                // find that neuron
399                                neu_counter = 0;
400                                int from;
401                                for (from = 0; from < org->cell_count; from++)
402                                {
403                                        if (org->C[from]->type == CELL_NEURON) neu_counter++;
404                                        if (from_index == (neu_counter - 1)) break;
405                                }
406                                if (from >= org->cell_count) goto wait_conn;
407                                neu_from = org->C[from];
408
409                                // add connection
410                                // error: could not add connection (too many?)
411                                if (addConnection(neu_from, weight))
412                                {
413                                        // cannot fix
414                                        org->setError(gcur->pos);
415                                        return;  // error code set -> stop further cells development
416                                }
417                                gcur = gcur->child;
418                                break;
419                        }
420                wait_conn:
421                        {
422                                // wait for other neurons to develop
423                                // if there are others still active
424
425                                int active_count = 0;
426                                for (int i = 0; i < org->cell_count; i++)
427                                        if (org->C[i]->active) active_count++;
428                                active = false; // next time when we reach wait_conn, we will not count ourselves as active (if we were the last active cell, we got a chance to continue development for one development step only)
429                                if (active_count > 0)
430                                        // there is at least one active (including ourselves), halt, try again
431                                        return;  // error code not set -> halt this development and yield to other cells to develop
432
433#ifdef TREAT_BAD_CONNECTIONS_AS_INVALID_GENO // MacKo 2023-04: there were so many invalid connections accumulating in the genotype (and stopping processing of the chain of gcur->child) that it looks like treating them as errors is better... in 2000's, Framsticks neurons were flexible when it comes to inputs and outputs (for example, when asked, muscles would provide an output too, and neurons that ignored inputs would still accept them when connected) so f4 could create connections pretty randomly, but after 2000's we attempt to respect neurons' getPreferredInputs() and getPreferredOutput() so the network of connections has more constraints.
434                                if (gcur->parent->name == "#")
435                                {
436                                        // MacKo 2023-04: Unfortunately the logic of multiplicating connections is not ideal...
437                                        //TREAT_BAD_CONNECTIONS_AS_INVALID_GENO without this "#" exception would break /*4*/<X>N:N#5<[1:1]>
438                                        // because every neuron wants to get an input from the neuron that will be created next
439                                        // and all is fine until the last created neuron, which wants to get an input from another one which will not be created
440                                        // (3 gets from 4, 4 gets from 5, 5 wants to get from 6 (relative connection offset for each of them is 1),
441                                        // but 6 will not get created and if we want to TREAT_BAD_CONNECTIONS_AS_INVALID_GENO, we produce an error...
442                                        // We would like to have this multiplication working, but OTAH we don't want to accept bad connections because then they tend to multiply as junk genes and bloat the genotype also causing more and more neutral mutations...
443                                        //so this condition and checking for "#" is a simple way to be kind to some, but not all, bad connections, and not raise errors. Perhaps too kind and we open the door for too many cases with invalid connections.
444                                        //Maybe it would be better to perform this check before addConnection(), seeing that for example we process the last iteration of the repetition counter? But how would we know that the (needed here) input neuron will not be developed later by other dividing cells...
445                                        active = true; //not sure if needed, but let this cell have the chance to continue for as long as many children in the tree are left
446                                        gcur = gcur->child;
447                                        return;  // error code not set -> halt this development and yield to other cells to develop
448                                }
449                                else
450                                {
451                                        //org->setError(gcur->pos); //in case setRepairRemove() would not always produce reasonable results
452                                        org->setRepairRemove(gcur->pos, gcur); //produces unexpected results? or NOT? TODO verify, some genotypes earlier produced strange outcomes of this repair (produced a valid genotype, but some neurons were multiplied/copied after repair - maybe because when a branch of '<' (or something else) is missing, the other branch is copied?)
453                                        return;  // error code set -> stop further cells development
454                                }
455#else
456                                // no more actives, cannot add connection, ignore, but treat not as an error - before 2023-04
457                                gcur = gcur->child;
458#endif
459                        }
460                        break;
461                        case ':':
462                        {
463                                // neuron parameter
464                                // error: not a neuron
465                                if (type != CELL_NEURON)
466                                {
467                                        // fix: delete it
468                                        org->setRepairRemove(gcur->pos, gcur);
469                                        return;  // error code set -> stop further cells development
470                                }
471                                switch (gcur->prop_symbol)
472                                {
473                                case '!':
474                                        if (gcur->prop_increase)
475                                                force += (1.0 - force) * 0.2;
476                                        else
477                                                force -= force * 0.2;
478                                        break;
479                                case '=':
480                                        if (gcur->prop_increase)
481                                                inertia += (1.0 - inertia) * 0.2;
482                                        else
483                                                inertia -= inertia * 0.2;
484                                        break;
485                                case '/':
486                                        if (gcur->prop_increase)
487                                                sigmo *= 1.4;
488                                        else
489                                                sigmo /= 1.4;
490                                        break;
491                                default:
492                                        org->setRepairRemove(gcur->pos, gcur);
493                                        return;  // error code set -> stop further cells development
494                                }
495                                gcur = gcur->child;
496                                break;
497                        }
498                        case ' ':
499                        case '\t':
500                        case '\n':
501                        case '\r':
502                        {
503                                // whitespace has no effect, should not occur
504                                // fix: delete it
505                                org->setRepairRemove(gcur->pos, gcur);
506                                return;  // error code set -> stop further cells development
507                        }
508                        default:
509                        {
510                                // error: unknown code
511                                string buf = "Unknown code '" + gcur->name + "'";
512                                logMessage("f4_Cell", "oneStep", LOG_ERROR, buf.c_str());
513                                org->setRepairRemove(gcur->pos, gcur);
514                                return;  // error code set -> stop further cells development
515                        }
516                        }
517                }
518                else
519                {
520                        genoRange.add(gcur->pos, gcur->pos + int(gcur->name.length()) + 2 - 1); // +2 for N:
521                        if (type != CELL_UNDIFF)
522                        {
523                                // fix: delete this node
524                                org->setRepairRemove(gcur->pos, gcur);
525                                return;  // error code set -> stop further cells development
526                        }
527                        // error: if no previous
528                        if (dadlink == NULL)
529                        {
530                                // fix: delete it
531                                org->setRepairRemove(gcur->pos, gcur);
532                                return;  // error code set -> stop further cells development
533                        }
534                        neuclass = gcur->neuclass;
535                        type = CELL_NEURON;
536                        // change of type also halts development, to give other
537                        // cells a chance for adjustment.  Namely, it is important
538                        // to wait for other cells to turn to neurons before adding connections
539                        gcur = gcur->child;
540                        return;  // error code not set -> halt this development and yield to other cells to develop
541                }
542        }
543        active = false;  // done
544}
545
546
547int f4_Cell::addConnection(f4_Cell *nfrom, double nweight)
548{
549        if (nfrom->neuclass->getPreferredOutput() == 0) return -1; // if incoming neuron does not produce output, return error
550        if (neuclass->getPreferredInputs() != -1 && conns_count >= neuclass->getPreferredInputs()) return -1; //cannot add more inputs to this neuron
551        if (conns_count >= F4_MAX_CELL_INPUTS - 1) return -1; // over hardcoded limit
552        conns[conns_count] = new f4_CellConn(nfrom, nweight);
553        conns_count++;
554        return 0;
555}
556
557
558void f4_Cell::adjustRec()
559{
560        //f4_OrientMat rot;
561        int i;
562
563        if (recProcessedFlag)
564                // already processed
565                return;
566
567        // mark it processed
568        recProcessedFlag = 1;
569
570        // make sure its parent is processed first
571        if (dadlink != NULL)
572                dadlink->adjustRec();
573
574        // count children
575        childcount = 0;
576        for (i = 0; i < org->cell_count; i++)
577        {
578                if (org->C[i]->dadlink == this)
579                        if (org->C[i]->type == CELL_STICK)
580                                childcount++;
581        }
582
583        if (type == CELL_STICK)
584        {
585                if (dadlink == NULL)
586                {
587                        //firstend = Pt3D_0;
588                        // rotation due to rolling
589                        xrot = rolling;
590                        mz = 1;
591                }
592                else
593                {
594                        //firstend = dadlink->lastend;
595                        GeneProps Pdad = dadlink->P;
596                        GeneProps Padj = Pdad;
597                        Padj.propagateAlong(false);
598
599                        //rot = Orient_1;
600
601                        // rotation due to rolling
602                        xrot = rolling +
603                                // rotation due to twist
604                                Pdad.twist;
605                        if (dadlink->commacount <= 1)
606                        {
607                                // rotation due to curvedness
608                                zrot = Padj.curvedness;
609                        }
610                        else
611                        {
612                                zrot = Padj.curvedness + (anglepos * 1.0 / (dadlink->commacount + 1) - 0.5) * M_PI * 2.0;
613                        }
614
615                        //rot = rot * f4_OrientMat(yOz, xrot);
616                        //rot = rot * f4_OrientMat(xOy, zrot);
617                        // rotation relative to parent stick
618                        //OM = rot * OM;
619
620                        // rotation in world coordinates
621                        //OM =  ((f4_OrientMat)dadlink->OM) * OM;
622                        mz = dadlink->mz / dadlink->childcount;
623                }
624                //Pt3D lastoffset = (Orient)OM * (Pt3D(1,0,0)*P.len);
625                //lastend = firstend + lastoffset;
626        }
627}
628
629
630
631f4_CellConn::f4_CellConn(f4_Cell *nfrom, double nweight)
632{
633        from = nfrom;
634        weight = nweight;
635}
636
637
638
639f4_Cells::f4_Cells(f4_Node *genome, bool nrepair)
640{
641        repair = nrepair;
642        errorcode = GENOPER_OK;
643        errorpos = -1;
644        repair_remove = NULL;
645        repair_parent = NULL;
646        repair_insert = NULL;
647        tmpcel = NULL;
648
649        // create ancestor cell
650        C[0] = new f4_Cell(this, 0, genome, genome, NULL, 0, GeneProps::standard_values);
651        cell_count = 1;
652}
653
654
655
656f4_Cells::~f4_Cells()
657{
658        // release cells
659        if (cell_count)
660        {
661                for (int i = cell_count - 1; i >= 0; i--)
662                        delete C[i];
663                cell_count = 0;
664        }
665}
666
667
668bool f4_Cells::oneStep()
669{
670        int old_cell_count = cell_count; //cell_count may change in the loop as new cells may be appended because cells may be dividing
671        for (int i = 0; i < old_cell_count; i++)
672        {
673                C[i]->oneStep(); //keeps calling independently of C[i]->active
674                if (errorcode != GENOPER_OK)
675                {
676                        // error
677                        C[i]->active = false;  // stop
678                        return false;
679                }
680        }
681        for (int i = 0; i < cell_count; i++) //we check all cells, including newly created ones
682                if (C[i]->active)
683                        return true; //at least one cell is still active. TODO maybe the development should stop NOT because of the "active" field (there is this strange "yielding" state too), but by observing the progress of all cells and continuing the development while (errorcode==GENOPER_OK AND (gcur of at least one cell changed OR cell_count changed)) ?
684        return false;
685}
686
687
688int f4_Cells::simulate()
689{
690        const bool PRINT_CELLS_DEVELOPMENT = false; //print the state of cells
691        errorcode = GENOPER_OK;
692
693        for (int i = 0; i < cell_count; i++)  C[i]->active = true;
694
695        if (PRINT_CELLS_DEVELOPMENT) f4_Node::print_tree(C[0]->genot, 0);
696        if (PRINT_CELLS_DEVELOPMENT) print_cells("Initialization");
697
698        // execute oneStep() in a cycle
699        while (oneStep()) if (PRINT_CELLS_DEVELOPMENT) print_cells("Development step");
700        if (PRINT_CELLS_DEVELOPMENT) print_cells("After last development step");
701
702#ifdef EXTRA_STEP_CELL_DEVELOPMENT
703        if (errorcode == GENOPER_OK)
704        {
705                oneStep(); if (PRINT_CELLS_DEVELOPMENT) print_cells("After extra step"); //for these "halted" (yielding) cells (they have active==false) that wait for other cells to develop. Without this step, the last, recently halted one(s) may miss the "retrying" step if all active==true cells became active==false in the last step.
706        }
707#endif
708
709        if (errorcode != GENOPER_OK) return errorcode;
710
711        // fix neuron attachements
712        for (int i = 0; i < cell_count; i++)
713        {
714                if (C[i]->type == CELL_NEURON)
715                {
716                        while (C[i]->dadlink->type == CELL_NEURON)
717                        {
718                                C[i]->dadlink = C[i]->dadlink->dadlink;
719                        }
720                }
721        }
722
723        // there should be no undiff. cells
724        // make undifferentiated cells sticks
725        for (int i = 0; i < cell_count; i++)
726        {
727                if (C[i]->type == CELL_UNDIFF)
728                {
729                        C[i]->type = CELL_STICK;
730                        //setError();
731                }
732        }
733
734        // recursive adjust
735        // reset recursive traverse flags
736        for (int i = 0; i < cell_count; i++)
737                C[i]->recProcessedFlag = 0;
738        // process every cell
739        for (int i = 0; i < cell_count; i++)
740                C[i]->adjustRec();
741
742        //DB( printf("Cell simulation done, %d cells. \n", nc); )
743
744        if (PRINT_CELLS_DEVELOPMENT) print_cells("Final");
745
746        return errorcode;
747}
748
749
750void f4_Cells::print_cells(const char* description)
751{
752        printf("------ %-55s ------ errorcode=%d, errorpos=%d\n", description, getErrorCode(), getErrorPos());
753        for (int i = 0; i < cell_count; i++)
754        {
755                f4_Cell *c = C[i];
756                string type;
757                switch (c->type)
758                {
759                case CELL_UNDIFF: type = "undiff"; break;
760                case CELL_STICK:  type = "STICK"; break;
761                case CELL_NEURON: type = string("NEURON:") + c->neuclass->name.c_str(); break;
762                default: type = std::to_string(c->type);
763                }
764                const char *status = c->active ? "active" : (c->gcur != NULL ? "yielding" : ""); //yielding = not active but waiting for other cells
765                printf("%2d(%-8s)  nr=%d \t type=%-15s \t genot=%s \t gcurrent=%s", i, status, c->nr, type.c_str(), c->genot->name.c_str(), c->gcur ? c->gcur->name.c_str() : "null");
766                if (c->gcur && c->gcur->name == "[")
767                        printf("\tfrom=%d  weight=%g", c->gcur->conn_from, c->gcur->conn_weight);
768                printf("\n");
769                for (int l = 0; l < c->conns_count; l++)
770                        printf("\tconn:%d from=%d weight=%g\n", l, c->conns[l]->from->nr, c->conns[l]->weight);
771        }
772        printf("\n");
773}
774
775
776void f4_Cells::addCell(f4_Cell *newcell)
777{
778        if (cell_count >= F4_MAX_CELLS - 1)
779        {
780                delete newcell;
781                return;
782        }
783        C[cell_count] = newcell;
784        cell_count++;
785}
786
787
788void f4_Cells::setError(int nerrpos)
789{
790        errorcode = GENOPER_OPFAIL;
791        errorpos = nerrpos;
792}
793
794void f4_Cells::setRepairRemove(int nerrpos, f4_Node *to_remove)
795{
796        errorcode = GENOPER_REPAIR;
797        errorpos = nerrpos;
798        if (!repair)
799        {
800                // not in repair mode, treat as repairable error
801        }
802        else
803        {
804                repair_remove = to_remove;
805        }
806}
807
808int f4_Cells::setRepairInsert(int nerrpos, f4_Node *parent, f4_Node *to_insert)
809{
810        errorcode = GENOPER_REPAIR;
811        errorpos = nerrpos;
812        if (!repair)
813        {
814                // not in repair mode, treat as repairable error
815                return -1;
816        }
817        else
818        {
819                repair_parent = parent;
820                repair_insert = to_insert;
821                return 0;
822        }
823}
824
825void f4_Cells::repairGeno(f4_Node *geno, int whichchild)
826{
827        // assemble repaired geno, if the case
828        if (!repair) return;
829        if ((repair_remove == NULL) && (repair_insert == NULL)) return;
830        // traverse genotype tree, remove / insert node
831        f4_Node *g2;
832        if (whichchild == 1)
833                g2 = geno->child;
834        else
835                g2 = geno->child2;
836        if (g2 == NULL)
837                return;
838        if (g2 == repair_remove)
839        {
840                f4_Node *oldgeno;
841                geno->removeChild(g2);
842                if (g2->child)
843                {
844                        // add g2->child as child to geno
845                        if (whichchild == 1)
846                                geno->child = g2->child;
847                        else
848                                geno->child2 = g2->child;
849                        g2->child->parent = geno;
850                }
851                oldgeno = g2;
852                oldgeno->child = NULL;
853                delete oldgeno;
854                if (geno->child == NULL) return;
855                // check this new
856                repairGeno(geno, whichchild);
857                return;
858        }
859        if (g2 == repair_parent)
860        {
861                geno->removeChild(g2);
862                geno->addChild(repair_insert);
863                repair_insert->parent = geno;
864                repair_insert->child = g2;
865                repair_insert->child2 = NULL;
866                g2->parent = repair_insert;
867        }
868        // recurse
869        if (g2->child)  repairGeno(g2, 1);
870        if (g2->child2) repairGeno(g2, 2);
871}
872
873
874void f4_Cells::toF1Geno(SString &out)
875{
876        if (tmpcel) delete tmpcel;
877        tmpcel = new f4_Cell(-1, NULL, 0, GeneProps::standard_values);
878        out = "";
879        toF1GenoRec(0, out);
880        delete tmpcel;
881}
882
883
884void f4_Cells::toF1GenoRec(int curc, SString &out)
885{
886        int i, j, ccount;
887        f4_Cell *thisti;
888        f4_Cell *thneu;
889        char buf[200];
890
891        if (curc >= cell_count) return;
892
893        if (C[curc]->type != CELL_STICK) return;
894
895        thisti = C[curc];
896        if (thisti->dadlink != NULL)
897                *tmpcel = *(thisti->dadlink);
898
899        // adjust length, curvedness, etc.
900        tmpcel->P.propagateAlong(false);
901        while (tmpcel->P.length > thisti->P.length)
902        {
903                tmpcel->P.executeModifier('l');
904                out += "l";
905        }
906        while (tmpcel->P.length < thisti->P.length)
907        {
908                tmpcel->P.executeModifier('L');
909                out += "L";
910        }
911        while (tmpcel->P.curvedness > thisti->P.curvedness)
912        {
913                tmpcel->P.executeModifier('c');
914                out += "c";
915        }
916        while (tmpcel->P.curvedness < thisti->P.curvedness)
917        {
918                tmpcel->P.executeModifier('C');
919                out += "C";
920        }
921        while (thisti->rolling > 0.0f)
922        {
923                rolling_dec(&(thisti->rolling));
924                out += "R";
925        }
926        while (thisti->rolling < 0.0f)
927        {
928                rolling_inc(&(thisti->rolling));
929                out += "r";
930        }
931
932        // output X for this stick
933        out += "X";
934
935        // neurons attached to it
936        for (i = 0; i < cell_count; i++)
937        {
938                if (C[i]->type == CELL_NEURON)
939                {
940                        if (C[i]->dadlink == thisti)
941                        {
942                                thneu = C[i];
943                                out += "[";
944                                // ctrl
945                                //if (1 == thneu->ctrl) out += "@"; // old code; this can be easily generalized to any neuroclass if ever needed
946                                //if (2 == thneu->ctrl) out += "|";
947                                out += thneu->neuclass->name.c_str(); // not tested, but something like that
948                                // connections
949                                for (j = 0; j < thneu->conns_count; j++)
950                                {
951                                        if (j) out += ",";
952                                        sprintf(buf, "%d", thneu->conns[j]->from->nr - thneu->nr);
953                                        out += buf;
954                                        out += ":";
955                                        // connection weight
956                                        sprintf(buf, "%g", thneu->conns[j]->weight);
957                                        out += buf;
958                                }
959                                out += "]";
960                        }
961                }
962        }
963
964        // sticks connected to it
965        if (thisti->commacount >= 2)
966                out += "(";
967
968        ccount = 1;
969        for (i = 0; i < cell_count; i++)
970        {
971                if (C[i]->type == CELL_STICK)
972                {
973                        if (C[i]->dadlink == thisti)
974                        {
975                                while (ccount < (C[i])->anglepos)
976                                {
977                                        ccount++;
978                                        out += ",";
979                                }
980                                toF1GenoRec(i, out);
981                        }
982                }
983        }
984
985        while (ccount < thisti->commacount)
986        {
987                ccount++;
988                out += ",";
989        }
990
991        if (thisti->commacount >= 2)
992                out += ")";
993}
994
995
996
997// to organize an f4 genotype in a tree structure
998
999f4_Node::f4_Node()
1000{
1001        name = "?";
1002        parent = NULL;
1003        child = NULL;
1004        child2 = NULL;
1005        pos = -1;
1006
1007        reps = 0;
1008        prop_symbol = '\0';
1009        prop_increase = false;
1010        conn_from = 0;
1011        conn_weight = 0.0;
1012        neuclass = NULL;
1013}
1014
1015f4_Node::f4_Node(string nname, f4_Node *nparent, int npos)
1016{
1017        name = nname;
1018        parent = nparent;
1019        child = NULL;
1020        child2 = NULL;
1021        pos = npos;
1022        if (parent) parent->addChild(this);
1023
1024        reps = 0;
1025        prop_symbol = '\0';
1026        prop_increase = false;
1027        conn_from = 0;
1028        conn_weight = 0.0;
1029        neuclass = NULL;
1030}
1031
1032f4_Node::f4_Node(char nname, f4_Node *nparent, int npos)
1033{
1034        name = nname;
1035        parent = nparent;
1036        child = NULL;
1037        child2 = NULL;
1038        pos = npos;
1039        if (parent) parent->addChild(this);
1040
1041        reps = 0;
1042        prop_symbol = '\0';
1043        prop_increase = false;
1044        conn_from = 0;
1045        conn_weight = 0.0;
1046        neuclass = NULL;
1047}
1048
1049f4_Node::~f4_Node()
1050{
1051        destroy();
1052}
1053
1054void f4_Node::print_tree(const f4_Node *root, int indent)
1055{
1056        for (int i = 0; i < indent; i++) printf(" ");
1057        printf("%s%s%s (%d)", root->neuclass != NULL ? "N:" : "", root->name.c_str(), root->name == "#" ? std::to_string(root->reps).c_str() : "", root->count() - 1);
1058        if (root->name == "[")
1059                printf("     from=%-3d  weight=%g", root->conn_from, root->conn_weight);
1060        printf("\n");
1061        if (root->child) print_tree(root->child, indent + 1);
1062        if (root->child2) print_tree(root->child2, indent + 1);
1063}
1064
1065int f4_Node::addChild(f4_Node *nchi)
1066{
1067        if (child == NULL)
1068        {
1069                child = nchi;
1070                return 0;
1071        }
1072        if (child2 == NULL)
1073        {
1074                child2 = nchi;
1075                return 0;
1076        }
1077        return -1;
1078}
1079
1080int f4_Node::removeChild(f4_Node *nchi)
1081{
1082        if (nchi == child2)
1083        {
1084                child2 = NULL;
1085                return 0;
1086        }
1087        if (nchi == child)
1088        {
1089                child = NULL;
1090                return 0;
1091        }
1092        return -1;
1093}
1094
1095int f4_Node::childCount()
1096{
1097        return int(child != NULL) + int(child2 != NULL); //0, 1 or 2
1098}
1099
1100int f4_Node::count() const
1101{
1102        int c = 1;
1103        if (child != NULL)  c += child->count();
1104        if (child2 != NULL) c += child2->count();
1105        return c;
1106}
1107
1108f4_Node* f4_Node::ordNode(int n)
1109{
1110        int n1;
1111        if (n == 0) return this;
1112        n--;
1113        if (child != NULL)
1114        {
1115                n1 = child->count();
1116                if (n < n1) return child->ordNode(n);
1117                n -= n1;
1118        }
1119        if (child2 != NULL)
1120        {
1121                n1 = child2->count();
1122                if (n < n1) return child2->ordNode(n);
1123                n -= n1;
1124        }
1125        return NULL;
1126}
1127
1128f4_Node* f4_Node::randomNode()
1129{
1130        int n = count();
1131        // pick a random node between 0 and n-1
1132        return ordNode(rndUint(n));
1133}
1134
1135f4_Node* f4_Node::randomNodeWithSize(int mn, int mx)
1136{
1137        // try random nodes, and accept if size in range
1138        // limit to maxlim tries
1139        int i, n, maxlim;
1140        f4_Node *nod = NULL;
1141        maxlim = count();
1142        for (i = 0; i < maxlim; i++)
1143        {
1144                nod = randomNode();
1145                n = nod->count();
1146                if ((n >= mn) && (n <= mx)) return nod;
1147        }
1148        // failed, doesn't matter
1149        return nod;
1150}
1151
1152void f4_Node::sprint(SString& out)
1153{
1154        char buf2[20];
1155        // special case: repetition code
1156        if (name == "#")
1157        {
1158                out += "#";
1159                sprintf(buf2, "%d", reps);
1160                out += buf2;
1161        }
1162        else {
1163                // special case: neuron connection
1164                if (name == "[")
1165                {
1166                        out += "[";
1167                        sprintf(buf2, "%d", conn_from);
1168                        out += buf2;
1169                        sprintf(buf2, ":%g]", conn_weight);
1170                        out += buf2;
1171                }
1172                else if (name == ":")
1173                {
1174                        sprintf(buf2, ":%c%c:", prop_increase ? '+' : '-', prop_symbol);
1175                        out += buf2;
1176                }
1177                else if (neuclass != NULL)
1178                {
1179                        out += "N:";
1180                        out += neuclass->name.c_str();
1181                }
1182                else
1183                {
1184                        out += name.c_str();
1185                }
1186        }
1187
1188        if (child != NULL)
1189                child->sprint(out);
1190        // if two children, make sure last char is a '>'
1191        if (childCount() == 2)
1192                if (out[0] == 0) out += ">"; else
1193                        if (out[out.length() - 1] != '>') out += ">";
1194
1195        if (child2 != NULL)
1196                child2->sprint(out);
1197        // make sure last char is a '>'
1198        if (out[0] == 0) out += ">"; else
1199                if (out[out.length() - 1] != '>') out += ">";
1200}
1201
1202void f4_Node::sprintAdj(char *& buf)
1203{
1204        unsigned int len;
1205        // build in a SString, with initial size
1206        SString out;
1207        out.reserve(int(strlen(buf)) + 2000);
1208
1209        sprint(out);
1210        len = out.length();
1211
1212        // very last '>' can be omitted
1213        // MacKo 2023-05: after tightening parsing and removing a silent repair for missing '>' after '#', this is no longer always the case.
1214        // For genotypes using '#', removing trailing >'s makes them invalid: /*4*/<X><N:N>X#1>> or /*4*/<X><N:N>X#1#2>>> or /*4*/<X><N:N>X#1#2#3>>>> etc.
1215        // Such invalid genotypes with missing >'s would then require silently adding >'s, but now stricter parsing and clear information about invalid syntax is preferred.
1216        // See also comments in f4_processRecur() case '#'.
1217        //if (len > 1)
1218        //      if (out[len - 1] == '>') { (out.directWrite())[len - 1] = 0; out.endWrite(); }; //Macko 2023-04 "can be omitted" => was always removed in generated genotypes.
1219
1220        // copy back to string
1221        // if new is longer, reallocate buf
1222        if (len + 1 > strlen(buf))
1223        {
1224                buf = (char*)realloc(buf, len + 1);
1225        }
1226        strcpy(buf, out.c_str());
1227}
1228
1229f4_Node* f4_Node::duplicate()
1230{
1231        f4_Node *copy;
1232        copy = new f4_Node(*this);
1233        copy->parent = NULL;  // set later
1234        copy->child = NULL;
1235        copy->child2 = NULL;
1236        if (child != NULL)
1237        {
1238                copy->child = child->duplicate();
1239                copy->child->parent = copy;
1240        }
1241        if (child2 != NULL)
1242        {
1243                copy->child2 = child2->duplicate();
1244                copy->child2->parent = copy;
1245        }
1246        return copy;
1247}
1248
1249
1250void f4_Node::destroy()
1251{
1252        // children are destroyed (recursively) through the destructor
1253        if (child2 != NULL) delete child2;
1254        if (child != NULL) delete child;
1255}
1256
1257// scan genotype string and build a tree
1258// return >1 for error (errorpos)
1259int f4_processRecur(const char* genot, const int genot_len, int &pos_inout, f4_Node *parent)
1260{
1261        static const char *all_modifiers_no_comma = F14_MODIFIERS; //I did experiments with added comma (see all_modifiers_for_simplify below) which had the advantage of commas not breaking sequences of modifiers, thus longer sequences of modifiers (including commas) could be simplified and genetic bloat was further reduced. But since we impose a limit on the number of modifier chars in GenoOperators::simplifiedModifiers(), it would also influence commas (e.g. no more than 8 commas per sequence), so in order to leave commas entirely unlimited let's exclude them from simplification. Note that currently 'X' or any other non-F14_MODIFIERS char also separates the sequence to be simplified, so if we wanted a really intensive simplification, it should occur during development, when we know precisely which genes influence each f4_Cell.
1262        //const char *Geno_f4::all_modifiers_for_simplify = F14_MODIFIERS ",\1"; //'\1' added to keep the number of chars even, avoid exceptions in logic and save the simple rule that the sequence is made of pairs (gene,contradictory gene), where a comma has no contradictory gene and \1 is unlikely to occur in the f4 genotype (and not allowed), so no risk it will cancel out a comma during simplification.
1263
1264
1265        f4_Node *par = parent;
1266
1267        if (pos_inout >= genot_len)
1268                return genot_len + 1;
1269
1270        while (pos_inout < genot_len)
1271        {
1272                const bool PRINT_PARSING_LOCATION = false;
1273                if (PRINT_PARSING_LOCATION)
1274                {
1275                        printf("%s\n", genot);
1276                        for (int i = 0; i < pos_inout; i++) printf(" ");
1277                        printf("^\n");
1278                }
1279                switch (genot[pos_inout])
1280                {
1281                case '<':
1282                {
1283                        f4_Node *node = new f4_Node("<", par, pos_inout);
1284                        par = node;
1285                        pos_inout++; //move after '<'
1286                        int res = f4_processRecur(genot, genot_len, pos_inout, par);
1287                        if (res) return res;
1288                        if (pos_inout < genot_len)
1289                        {
1290                                res = f4_processRecur(genot, genot_len, pos_inout, par);
1291                                if (res) return res;
1292                        }
1293                        else // ran out
1294                        {
1295                                //MacKo 2023-04, more strict behavior: instead of silent repair (no visible effect to the user, genotype stays invalid but is interpreted and reported as valid), we now point out where the error is. For example <X> or <X><X or <X><N:N>
1296                                return genot_len + 1;
1297                                //old silent repair:
1298                                //node = new f4_Node(">", par, genot_len - 1);
1299                        }
1300                        return 0;  // OK
1301                }
1302                case '>':
1303                {
1304                        new f4_Node(">", par, pos_inout);
1305                        pos_inout++; //move after '>'
1306                        return 0;  // OK
1307                }
1308                case '#':
1309                {
1310                        // repetition marker
1311                        ExtValue reps;
1312                        const char* end = reps.parseNumber(genot + pos_inout + 1, ExtPType::TInt);
1313                        if (end == NULL)
1314                                return pos_inout + 1; //error
1315                        f4_Node *node = new f4_Node("#", par, pos_inout); //TODO here or elsewhere: gene mapping seems to map '#' but not the following number
1316                        node->reps = reps.getInt();
1317                        // skip number
1318                        pos_inout += end - (genot + pos_inout);
1319                        int res = f4_processRecur(genot, genot_len, pos_inout, node);
1320                        if (res) return res;
1321                        if (pos_inout < genot_len)
1322                        {
1323                                res = f4_processRecur(genot, genot_len, pos_inout, node);
1324                                if (res) return res;
1325                        }
1326                        else // ran out
1327                        {
1328                                return genot_len + 1; //MacKo 2023-04: report an error, better to be more strict instead of a silent repair (genotype stays invalid but is interpreted and reported as valid) with non-obvious consequences?
1329                                //earlier approach - silently treating this problem (we don't ever see where the error is because it gets corrected in some way here, while parsing the genotype, and error location in the genotype is never reported):
1330                                //node = new f4_Node(">", par, genot_len - 1); // Maybe TODO: check if this was needed and if this was really the best repair operation; could happen many times in succession for some genotypes even though they were only a result of f4 operators, not manually created... and the operators should not generate invalid genotypes, right? Or maybe crossover does? Seemed like too many #n's for closing >'s; removing #n or adding > helped. Examples (remove trailing >'s to make invalid): /*4*/<X><N:N>X#1>> or /*4*/<X><N:N>X#1#2>>> or /*4*/<X><N:N>X#1#2#3>>>> etc.
1331                                // So operators somehow don't do it properly sometimes? But F4_ADD_REP adds '>'... Maybe the rule to always remove final trailing '>' was responsible? (now commented out). Since the proper syntax for # is #n ...repcode... > ...endcode..., perhaps endcode also needs '>' as the final delimiter. If we have many #'s in the genotype and the final >'s are missing, in the earlier approach we would keep adding them here as needed to ensure the syntax is valid. If we don't add '>' here silently, they must be explicitly added or else the genotype is invalid. BUT this earlier approach here only handled the situation where the genotype ended prematurely; what about cases where '>' may be needed as delimiters for # in the middle of the genotype? Or does # always concern all genes until the end, unless explicitly delimited earlier? Perhaps, if the '>' endcode delimiters are not present in the middle of the genotype, we don't know where they should be so the earlier approach would always add them only at the end of the genotype?
1332                        }
1333                        return 0;  // OK
1334                }
1335                case ' ':
1336                case '\n':
1337                case '\r':
1338                case '\t':
1339                {
1340                        // whitespace: ignore
1341                        pos_inout++;
1342                        break;
1343                }
1344                case 'N':
1345                {
1346                        int forgenorange = pos_inout;
1347                        if (genot[pos_inout + 1] != ':')
1348                                return pos_inout + 1; //error
1349                        pos_inout += 2; //skipping "N:"
1350                        unsigned int neuroclass_begin = pos_inout;
1351                        char* neuroclass_end = (char*)genot + neuroclass_begin;
1352                        NeuroClass *neuclass = GenoOperators::parseNeuroClass(neuroclass_end, ModelEnum::SHAPETYPE_BALL_AND_STICK); //advances neuroclass_end
1353                        if (neuclass == NULL)
1354                                return pos_inout + 1; //error
1355                        pos_inout += neuroclass_end - genot - neuroclass_begin;
1356                        string neutype = string(genot + neuroclass_begin, genot + pos_inout);
1357                        f4_Node *node = new f4_Node(neutype, par, forgenorange);
1358                        node->neuclass = neuclass;
1359                        par = node;
1360                        // if it continues with a colon that determines a neuron parameter (e.g. N:N:+=: ), then let the switch case for colon handle this
1361                        break;
1362                }
1363                case ':':
1364                {
1365                        // neuron parameter  +! -! += -= +/ or -/
1366                        // in the future this could be generalized to all neuron properties, for example N:|:power:0.6:range:1.4, or can even use '=' or ',' instead of ':' if no ambiguity
1367                        char prop_dir, prop_symbol, prop_end[2]; // prop_end is only to ensure that neuron parameter definition is completed
1368                        if (sscanf(genot + pos_inout, ":%c%c%1[:]", &prop_dir, &prop_symbol, &prop_end) != 3)
1369                                // error: incorrect format
1370                                return pos_inout + 1 + 1;
1371                        if (prop_dir != '-' && prop_dir != '+')
1372                                return pos_inout + 1 + 1; //error
1373                        switch (prop_symbol)
1374                        {
1375                        case '!':  case '=':  case '/':  break;
1376                        default:
1377                                return pos_inout + 1 + 1; //error
1378                        }
1379                        f4_Node *node = new f4_Node(":", par, pos_inout);
1380                        node->prop_symbol = prop_symbol;
1381                        node->prop_increase = prop_dir == '+' ? true : false; // + or -
1382                        par = node;
1383                        pos_inout += 4; //skipping :ds:
1384                        break;
1385                }
1386                case '[':
1387                {
1388                        double weight = 0;
1389                        int relfrom;
1390                        const char *end = parseConnection(genot + pos_inout, relfrom, weight);
1391                        if (end == NULL)
1392                                return pos_inout + 1; //error
1393
1394                        f4_Node *node = new f4_Node("[", par, pos_inout);
1395                        node->conn_from = relfrom;
1396                        node->conn_weight = weight;
1397                        par = node;
1398                        pos_inout += end - (genot + pos_inout);
1399                        break;
1400                }
1401                default: // 'X' and ',' and all modifiers and also invalid symbols - add a node. For symbols that are not valid in f4, the cell development process will give the error or repair
1402                {
1403                        //printf("any regular character '%c'\n", genot[pos_inout]);
1404#define F4_SIMPLIFY_MODIFIERS //avoid long sequences like ...<X>llmlIilImmimiimmimifmfl<fifmmimilimmmiimiliffmfliIfififlliflimfliffififmiffmflllfflimlififfiiffifIr<r<... - another option, instead of simplifying while parsing here, would be mutations: when they add/modify/remove a modifier node, they could "clean" the tree by removing nodes when they encounter contradictory modifiers on the same subpath, and also limit the number of modifiers just as GenoOperators::simplifiedModifiers() does.
1405#ifdef F4_SIMPLIFY_MODIFIERS
1406                        char *ptr = (char*)(genot + pos_inout);
1407
1408#ifdef __BORLANDC__ // "[bcc32c Error] cannot compile this non-trivial TLS destruction yet" (C++B 10.4u2)
1409                        static
1410#else
1411                        thread_local
1412#endif
1413                                vector<int> modifs_counts(strlen(all_modifiers_no_comma)); ///<an array with a known constant size storing counters of each modifier symbol from all_modifiers_no_comma, created once to avoid reallocation every time when modifier genes are simplified during parsing. Initialization of required size; it will never be resized.
1414                        std::fill(modifs_counts.begin(), modifs_counts.end(), 0); //zeroing only needed if we encountered a char from all_modifiers_no_comma and enter the 'while' loop below
1415
1416                        while (char *m = GenoOperators::strchrn0(all_modifiers_no_comma, *ptr)) //only processes a section of chars known in all_modifiers_no_comma, other characters will exit the loop
1417                        {
1418                                modifs_counts[m - all_modifiers_no_comma]++;
1419                                GenoOperators::skipWS(++ptr); //advance and ignore whitespace
1420                        }
1421                        int advanced = ptr - (genot + pos_inout);
1422                        if (advanced > 0) //found modifiers
1423                        {
1424                                string simplified = GenoOperators::simplifiedModifiers(all_modifiers_no_comma, modifs_counts);
1425                                // add a node for each char in "simplified"
1426                                for (size_t i = 0; i < simplified.length(); i++)
1427                                {
1428                                        int pos = GenoOperators::strchrn0(genot + pos_inout, simplified[i]) - genot; //unnecessarily finding the same char, if it occurrs multiple times in simplified
1429                                        f4_Node *node = new f4_Node(simplified[i], par, pos); //location is approximate. In the simplification process we don't trace where the origin(s) of the simplified[i] gene were. We provide 'pos' as the first occurrence of simplified[i] (for example, all 'L' will have the same location assigned, but at least this is where 'L' occurred in the genotype, so in case of any modification of a node (repair, removal, whatever... even mapping of genes) the indicated gene will be one of the responsible ones)
1430                                        par = node;
1431                                }
1432                                pos_inout += advanced;
1433                        }
1434                        else // genot[pos_inout] is a character not present in all_modifiers_no_comma, so treat it as a regular individual char just as it would be without simplification
1435                        {
1436                                f4_Node *node = new f4_Node(genot[pos_inout], par, pos_inout);
1437                                par = node;
1438                                pos_inout++;
1439                        }
1440#else
1441                        f4_Node *node = new f4_Node(genot[pos_inout], par, pos_inout);
1442                        par = node;
1443                        pos_inout++;
1444#endif // F4_SIMPLIFY_MODIFIERS
1445                        break;
1446                }
1447                }
1448        }
1449
1450        // should end with a '>'
1451        if (par && par->name != ">")
1452        {
1453                //happens when pos_inout == genot_len
1454                //return pos_inout; //MacKo 2023-04: could report an error instead of silent repair, but repair operators only work in Cells (i.e., after the f4_Node tree has been parsed without errors and Cells can start developing) so we don't want to make a fatal error because of missing '>' here. Also after conversions from Cells to text, trailing '>' is deliberately removed... and also the simplest genotype is officially X, not X>.
1455                new f4_Node('>', par, genot_len - 1);
1456        }
1457
1458        return 0;  // OK
1459}
1460
1461int f4_process(const char *genot, f4_Node *root)
1462{
1463        int pos = 0;
1464        int res = f4_processRecur(genot, (int)strlen(genot), pos, root);
1465        if (res > 0)
1466                return res; //parsing error
1467        else if (genot[pos] != 0)
1468                return pos + 1; //parsing OK but junk, unparsed genes left, for example /*4*/<X>N:N>whatever or /*4*/<X>X>>>
1469        else
1470                return 0; //parsing OK and parsed until the end
1471}
1472
1473const char* parseConnection(const char *fragm, int& relfrom, double &weight)
1474{
1475        const char *parser = fragm;
1476        if (*parser != '[') return NULL;
1477        parser++;
1478        ExtValue val;
1479        parser = val.parseNumber(parser, ExtPType::TInt);
1480        if (parser == NULL) return NULL;
1481        relfrom = val.getInt();
1482        if (*parser != ':') return NULL;
1483        parser++;
1484        parser = val.parseNumber(parser, ExtPType::TDouble);
1485        if (parser == NULL) return NULL;
1486        weight = val.getDouble();
1487        if (*parser != ']') return NULL;
1488        parser++;
1489        return parser;
1490}
1491
1492/*
1493f4_Node* f4_processTree(const char* geno)
1494{
1495        f4_Node *root = new f4_Node();
1496        int res = f4_processRecur(geno, 0, root);
1497        if (res) return NULL;
1498        //DB( printf("test f4  "); )
1499        DB(
1500                if (root->child)
1501                {
1502                        char* buf = (char*)malloc(300);
1503                        DB(printf("(%d) ", root->child->count());)
1504                                buf[0] = 0;
1505                        root->child->sprintAdj(buf);
1506                        DB(printf("%s\n", buf);)
1507                                free(buf);
1508                }
1509        )
1510                return root->child;
1511}
1512*/
Note: See TracBrowser for help on using the repository browser.