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

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

More robust stopping condition for organism development: no longer based on declarations of cells (I am active or I am not), but on the observation of their actual development progress

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