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

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

Don't remove trailing '>' from genotypes

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