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

Last change on this file since 1241 was 1241, checked in by Maciej Komosinski, 13 months ago

No longer sort modifiers and cancel out antagonistic modifiers in f1 and f4; simplifying modifier sequences is now much less intrusive to allow for 2N distinct values of properties instead of only 2*N that resulted from earlier forced ordering (N is the number of same-letter upper- and lower-case characters in a modifier sequence)

  • 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        //the same number of cells, no progress in development in any cell -> stagnation!
682
683        if (development_stagnation) // stagnation was already detected in the previous step, so end development!
684        {
685                for (int i = 0; i < cell_count; i++)
686                        if (C[i]->gcur != NULL) // genotype execution pointer did not reach the end
687                                logPrintf("f4_Cells", "oneStep", LOG_WARN, "Finishing the development of cells due to stagnation, but cell %d did not reach the end of development", i); //let's see an example of such a genotype and investigate...
688                return false; //end development
689        }
690        else
691        {
692                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
693                return true; //one grace step. If there are some waiting cells, they must move on in the next step and set development_stagnation=false or set error. If development_stagnation is not set to false, we will finish development in the next step. This grace step may be unnecessary if there are no waiting cells, but we have no easy way to check this from here (although we could check if all cells' gcur==NULL... would this be always equivalent? Maybe some cells may stagnate with gcur!=NULL and they are not waiting for neural connections to develop and this does not mean an error? Added LOG_WARN above to detect such cases. Anyway, for gcur==NULL, f4_Cell.oneStep() exits immediately, so one grace step is not a big overhead.)
694        }
695}
696
697
698int f4_Cells::simulate()
699{
700        const bool PRINT_CELLS_DEVELOPMENT = false; //print the state of cells
701        errorcode = GENOPER_OK;
702        development_stagnation = false; //will be detected by oneStep()
703
704        if (PRINT_CELLS_DEVELOPMENT) f4_Node::print_tree(C[0]->genot, 0);
705        if (PRINT_CELLS_DEVELOPMENT) print_cells("Initialization");
706
707        // execute oneStep() in a cycle
708        while (oneStep()) if (PRINT_CELLS_DEVELOPMENT) print_cells("Development step");
709        if (PRINT_CELLS_DEVELOPMENT) print_cells("After last development step");
710
711        if (errorcode != GENOPER_OK) return errorcode;
712
713        // fix neuron attachements
714        for (int i = 0; i < cell_count; i++)
715        {
716                if (C[i]->type == CELL_NEURON)
717                {
718                        while (C[i]->dadlink->type == CELL_NEURON)
719                        {
720                                C[i]->dadlink = C[i]->dadlink->dadlink;
721                        }
722                }
723        }
724
725        // there should be no undiff. cells
726        // make undifferentiated cells sticks
727        for (int i = 0; i < cell_count; i++)
728        {
729                if (C[i]->type == CELL_UNDIFF)
730                {
731                        C[i]->type = CELL_STICK;
732                        //setError();
733                }
734        }
735
736        // recursive adjust
737        // reset recursive traverse flags
738        for (int i = 0; i < cell_count; i++)
739                C[i]->recProcessedFlag = 0;
740        // process every cell
741        for (int i = 0; i < cell_count; i++)
742                C[i]->adjustRec();
743
744        //DB( printf("Cell simulation done, %d cells. \n", nc); )
745
746        if (PRINT_CELLS_DEVELOPMENT) print_cells("Final");
747
748        return errorcode;
749}
750
751
752void f4_Cells::print_cells(const char* description)
753{
754        printf("------ %-55s ------ errorcode=%d, errorpos=%d\n", description, getErrorCode(), getErrorPos());
755        for (int i = 0; i < cell_count; i++)
756        {
757                f4_Cell *c = C[i];
758                string type;
759                switch (c->type)
760                {
761                case CELL_UNDIFF: type = "undiff"; break;
762                case CELL_STICK:  type = "STICK"; break;
763                case CELL_NEURON: type = string("NEURON:") + c->neuclass->name.c_str(); break;
764                default: type = std::to_string(c->type);
765                }
766                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.
767                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");
768                if (c->gcur && c->gcur->name == "[")
769                        printf("\tfrom=%d  weight=%g", c->gcur->conn_from, c->gcur->conn_weight);
770                printf("\n");
771                for (int l = 0; l < c->conns_count; l++)
772                        printf("\tconn:%d from=%d weight=%g\n", l, c->conns[l]->from->nr, c->conns[l]->weight);
773        }
774        printf("\n");
775}
776
777
778void f4_Cells::addCell(f4_Cell *newcell)
779{
780        if (cell_count >= F4_MAX_CELLS - 1)
781        {
782                delete newcell;
783                return;
784        }
785        C[cell_count] = newcell;
786        cell_count++;
787}
788
789
790void f4_Cells::setError(int nerrpos)
791{
792        errorcode = GENOPER_OPFAIL;
793        errorpos = nerrpos;
794}
795
796void f4_Cells::setRepairRemove(int nerrpos, f4_Node *to_remove)
797{
798        errorcode = GENOPER_REPAIR;
799        errorpos = nerrpos;
800        if (!repair)
801        {
802                // not in repair mode, treat as repairable error
803        }
804        else
805        {
806                repair_remove = to_remove;
807        }
808}
809
810int f4_Cells::setRepairInsert(int nerrpos, f4_Node *parent, f4_Node *to_insert)
811{
812        errorcode = GENOPER_REPAIR;
813        errorpos = nerrpos;
814        if (!repair)
815        {
816                // not in repair mode, treat as repairable error
817                return -1;
818        }
819        else
820        {
821                repair_parent = parent;
822                repair_insert = to_insert;
823                return 0;
824        }
825}
826
827void f4_Cells::repairGeno(f4_Node *geno, int whichchild)
828{
829        // assemble repaired geno, if the case
830        if (!repair) return;
831        if ((repair_remove == NULL) && (repair_insert == NULL)) return;
832        // traverse genotype tree, remove / insert node
833        f4_Node *g2;
834        if (whichchild == 1)
835                g2 = geno->child;
836        else
837                g2 = geno->child2;
838        if (g2 == NULL)
839                return;
840        if (g2 == repair_remove)
841        {
842                f4_Node *oldgeno;
843                geno->removeChild(g2);
844                if (g2->child)
845                {
846                        // add g2->child as child to geno
847                        if (whichchild == 1)
848                                geno->child = g2->child;
849                        else
850                                geno->child2 = g2->child;
851                        g2->child->parent = geno;
852                }
853                oldgeno = g2;
854                oldgeno->child = NULL;
855                delete oldgeno;
856                if (geno->child == NULL) return;
857                // check this new
858                repairGeno(geno, whichchild);
859                return;
860        }
861        if (g2 == repair_parent)
862        {
863                geno->removeChild(g2);
864                geno->addChild(repair_insert);
865                repair_insert->parent = geno;
866                repair_insert->child = g2;
867                repair_insert->child2 = NULL;
868                g2->parent = repair_insert;
869        }
870        // recurse
871        if (g2->child)  repairGeno(g2, 1);
872        if (g2->child2) repairGeno(g2, 2);
873}
874
875
876void f4_Cells::toF1Geno(SString &out)
877{
878        if (tmpcel) delete tmpcel;
879        tmpcel = new f4_Cell(-1, NULL, 0, GeneProps::standard_values);
880        out = "";
881        toF1GenoRec(0, out);
882        delete tmpcel;
883}
884
885
886void f4_Cells::toF1GenoRec(int curc, SString &out)
887{
888        if (curc >= cell_count) return;
889
890        if (C[curc]->type != CELL_STICK) return;
891
892        f4_Cell *thisti = C[curc];
893        if (thisti->dadlink != NULL)
894                *tmpcel = *(thisti->dadlink);
895
896        // adjust length, curvedness, etc.
897        tmpcel->P.propagateAlong(false);
898        while (tmpcel->P.length > thisti->P.length)
899        {
900                tmpcel->P.executeModifier('l');
901                out += "l";
902        }
903        while (tmpcel->P.length < thisti->P.length)
904        {
905                tmpcel->P.executeModifier('L');
906                out += "L";
907        }
908        while (tmpcel->P.curvedness > thisti->P.curvedness)
909        {
910                tmpcel->P.executeModifier('c');
911                out += "c";
912        }
913        while (tmpcel->P.curvedness < thisti->P.curvedness)
914        {
915                tmpcel->P.executeModifier('C');
916                out += "C";
917        }
918        while (thisti->rolling > 0.0f)
919        {
920                rolling_dec(&(thisti->rolling));
921                out += "R";
922        }
923        while (thisti->rolling < 0.0f)
924        {
925                rolling_inc(&(thisti->rolling));
926                out += "r";
927        }
928
929        // output X for this stick
930        out += "X";
931
932        // neurons attached to it
933        for (int i = 0; i < cell_count; i++)
934        {
935                if (C[i]->type == CELL_NEURON)
936                {
937                        if (C[i]->dadlink == thisti)
938                        {
939                                f4_Cell *thneu = C[i];
940                                out += "[";
941                                out += thneu->neuclass->name.c_str();
942                                if (thneu->conns_count > 0)
943                                        out += ", ";
944                                // connections
945                                for (int j = 0; j < thneu->conns_count; j++)
946                                {
947                                        if (j > 0) out += ", ";
948                                        char buf[100];
949                                        sprintf(buf, "%d", thneu->conns[j]->from->nr - thneu->nr);
950                                        out += buf;
951                                        out += ":";
952                                        // connection weight
953                                        sprintf(buf, "%g", thneu->conns[j]->weight);
954                                        out += buf;
955                                }
956                                out += "]";
957                        }
958                }
959        }
960
961        // sticks connected to it
962        if (thisti->commacount >= 2)
963                out += "(";
964
965        int ccount = 1;
966        for (int i = 0; i < cell_count; i++)
967        {
968                if (C[i]->type == CELL_STICK)
969                {
970                        if (C[i]->dadlink == thisti)
971                        {
972                                while (ccount < (C[i])->anglepos)
973                                {
974                                        ccount++;
975                                        out += ",";
976                                }
977                                toF1GenoRec(i, out);
978                        }
979                }
980        }
981
982        while (ccount < thisti->commacount)
983        {
984                ccount++;
985                out += ",";
986        }
987
988        if (thisti->commacount >= 2)
989                out += ")";
990}
991
992
993
994// to organize an f4 genotype in a tree structure
995
996f4_Node::f4_Node()
997{
998        name = "?";
999        parent = NULL;
1000        child = NULL;
1001        child2 = NULL;
1002        pos = -1;
1003
1004        reps = 0;
1005        prop_symbol = '\0';
1006        prop_increase = false;
1007        conn_from = 0;
1008        conn_weight = 0.0;
1009        neuclass = NULL;
1010}
1011
1012f4_Node::f4_Node(string nname, f4_Node *nparent, int npos)
1013{
1014        name = nname;
1015        parent = nparent;
1016        child = NULL;
1017        child2 = NULL;
1018        pos = npos;
1019        if (parent) parent->addChild(this);
1020
1021        reps = 0;
1022        prop_symbol = '\0';
1023        prop_increase = false;
1024        conn_from = 0;
1025        conn_weight = 0.0;
1026        neuclass = NULL;
1027}
1028
1029f4_Node::f4_Node(char nname, f4_Node *nparent, int npos)
1030{
1031        name = nname;
1032        parent = nparent;
1033        child = NULL;
1034        child2 = NULL;
1035        pos = npos;
1036        if (parent) parent->addChild(this);
1037
1038        reps = 0;
1039        prop_symbol = '\0';
1040        prop_increase = false;
1041        conn_from = 0;
1042        conn_weight = 0.0;
1043        neuclass = NULL;
1044}
1045
1046f4_Node::~f4_Node()
1047{
1048        destroy();
1049}
1050
1051void f4_Node::print_tree(const f4_Node *root, int indent)
1052{
1053        for (int i = 0; i < indent; i++) printf(" ");
1054        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);
1055        if (root->name == "[")
1056                printf("     from=%-3d  weight=%g", root->conn_from, root->conn_weight);
1057        printf("\n");
1058        if (root->child) print_tree(root->child, indent + 1);
1059        if (root->child2) print_tree(root->child2, indent + 1);
1060}
1061
1062int f4_Node::addChild(f4_Node *nchi)
1063{
1064        if (child == NULL)
1065        {
1066                child = nchi;
1067                return 0;
1068        }
1069        if (child2 == NULL)
1070        {
1071                child2 = nchi;
1072                return 0;
1073        }
1074        return -1;
1075}
1076
1077int f4_Node::removeChild(f4_Node *nchi)
1078{
1079        if (nchi == child2)
1080        {
1081                child2 = NULL;
1082                return 0;
1083        }
1084        if (nchi == child)
1085        {
1086                child = NULL;
1087                return 0;
1088        }
1089        return -1;
1090}
1091
1092int f4_Node::childCount()
1093{
1094        return int(child != NULL) + int(child2 != NULL); //0, 1 or 2
1095}
1096
1097int f4_Node::count() const
1098{
1099        int c = 1;
1100        if (child != NULL)  c += child->count();
1101        if (child2 != NULL) c += child2->count();
1102        return c;
1103}
1104
1105f4_Node* f4_Node::ordNode(int n)
1106{
1107        int n1;
1108        if (n == 0) return this;
1109        n--;
1110        if (child != NULL)
1111        {
1112                n1 = child->count();
1113                if (n < n1) return child->ordNode(n);
1114                n -= n1;
1115        }
1116        if (child2 != NULL)
1117        {
1118                n1 = child2->count();
1119                if (n < n1) return child2->ordNode(n);
1120                n -= n1;
1121        }
1122        return NULL;
1123}
1124
1125f4_Node* f4_Node::randomNode()
1126{
1127        int n = count();
1128        // pick a random node between 0 and n-1
1129        return ordNode(rndUint(n));
1130}
1131
1132f4_Node* f4_Node::randomNodeWithSize(int mn, int mx)
1133{
1134        // try random nodes, and accept if size in range
1135        // limit to maxlim tries
1136        int i, n, maxlim;
1137        f4_Node *nod = NULL;
1138        maxlim = count();
1139        for (i = 0; i < maxlim; i++)
1140        {
1141                nod = randomNode();
1142                n = nod->count();
1143                if ((n >= mn) && (n <= mx)) return nod;
1144        }
1145        // failed, doesn't matter
1146        return nod;
1147}
1148
1149void f4_Node::sprint(SString& out)
1150{
1151        char buf2[20];
1152        // special case: repetition code
1153        if (name == "#")
1154        {
1155                out += "#";
1156                sprintf(buf2, "%d", reps);
1157                out += buf2;
1158        }
1159        else {
1160                // special case: neuron connection
1161                if (name == "[")
1162                {
1163                        out += "[";
1164                        sprintf(buf2, "%d", conn_from);
1165                        out += buf2;
1166                        sprintf(buf2, ":%g]", conn_weight);
1167                        out += buf2;
1168                }
1169                else if (name == ":")
1170                {
1171                        sprintf(buf2, ":%c%c:", prop_increase ? '+' : '-', prop_symbol);
1172                        out += buf2;
1173                }
1174                else if (neuclass != NULL)
1175                {
1176                        out += "N:";
1177                        out += neuclass->name.c_str();
1178                }
1179                else
1180                {
1181                        out += name.c_str();
1182                }
1183        }
1184
1185        if (child != NULL)
1186                child->sprint(out);
1187        // if two children, make sure last char is a '>'
1188        if (childCount() == 2)
1189                if (out[0] == 0) out += ">"; else
1190                        if (out[out.length() - 1] != '>') out += ">";
1191
1192        if (child2 != NULL)
1193                child2->sprint(out);
1194        // make sure last char is a '>'
1195        if (out[0] == 0) out += ">"; else
1196                if (out[out.length() - 1] != '>') out += ">";
1197}
1198
1199void f4_Node::sprintAdj(char *& buf)
1200{
1201        unsigned int len;
1202        // build in a SString, with initial size
1203        SString out;
1204        out.reserve(int(strlen(buf)) + 2000);
1205
1206        sprint(out);
1207        len = out.length();
1208
1209        // very last '>' can be omitted
1210        // MacKo 2023-05: after tightening parsing and removing a silent repair for missing '>' after '#', this is no longer always the case.
1211        // 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.
1212        // Such invalid genotypes with missing >'s would then require silently adding >'s, but now stricter parsing and clear information about invalid syntax is preferred.
1213        // See also comments in f4_processRecur() case '#'.
1214        //if (len > 1)
1215        //      if (out[len - 1] == '>') { (out.directWrite())[len - 1] = 0; out.endWrite(); }; //Macko 2023-04 "can be omitted" => was always removed in generated genotypes.
1216
1217        // copy back to string
1218        // if new is longer, reallocate buf
1219        if (len + 1 > strlen(buf))
1220        {
1221                buf = (char*)realloc(buf, len + 1);
1222        }
1223        strcpy(buf, out.c_str());
1224}
1225
1226f4_Node* f4_Node::duplicate()
1227{
1228        f4_Node *copy;
1229        copy = new f4_Node(*this);
1230        copy->parent = NULL;  // set later
1231        copy->child = NULL;
1232        copy->child2 = NULL;
1233        if (child != NULL)
1234        {
1235                copy->child = child->duplicate();
1236                copy->child->parent = copy;
1237        }
1238        if (child2 != NULL)
1239        {
1240                copy->child2 = child2->duplicate();
1241                copy->child2->parent = copy;
1242        }
1243        return copy;
1244}
1245
1246
1247void f4_Node::destroy()
1248{
1249        // children are destroyed (recursively) through the destructor
1250        if (child2 != NULL) delete child2;
1251        if (child != NULL) delete child;
1252}
1253
1254// scan genotype string and build a tree
1255// return >1 for error (errorpos)
1256int f4_processRecur(const char* genot, const int genot_len, int &pos_inout, f4_Node *parent)
1257{
1258        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.
1259        //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.
1260
1261
1262        f4_Node *par = parent;
1263
1264        if (pos_inout >= genot_len)
1265                return genot_len + 1;
1266
1267        while (pos_inout < genot_len)
1268        {
1269                const bool PRINT_PARSING_LOCATION = false;
1270                if (PRINT_PARSING_LOCATION)
1271                {
1272                        printf("%s\n", genot);
1273                        for (int i = 0; i < pos_inout; i++) printf(" ");
1274                        printf("^\n");
1275                }
1276                switch (genot[pos_inout])
1277                {
1278                case '<':
1279                {
1280                        f4_Node *node = new f4_Node("<", par, pos_inout);
1281                        par = node;
1282                        pos_inout++; //move after '<'
1283                        int res = f4_processRecur(genot, genot_len, pos_inout, par);
1284                        if (res) return res;
1285                        if (pos_inout < genot_len)
1286                        {
1287                                res = f4_processRecur(genot, genot_len, pos_inout, par);
1288                                if (res) return res;
1289                        }
1290                        else // ran out
1291                        {
1292                                //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>
1293                                return genot_len + 1;
1294                                //old silent repair:
1295                                //node = new f4_Node(">", par, genot_len - 1);
1296                        }
1297                        return 0;  // OK
1298                }
1299                case '>':
1300                {
1301                        new f4_Node(">", par, pos_inout);
1302                        pos_inout++; //move after '>'
1303                        return 0;  // OK
1304                }
1305                case '#':
1306                {
1307                        // repetition marker
1308                        ExtValue reps;
1309                        const char* end = reps.parseNumber(genot + pos_inout + 1, ExtPType::TInt);
1310                        if (end == NULL)
1311                                return pos_inout + 1; //error
1312                        f4_Node *node = new f4_Node("#", par, pos_inout); //TODO here or elsewhere: gene mapping seems to map '#' but not the following number
1313                        node->reps = reps.getInt();
1314                        // skip number
1315                        pos_inout += end - (genot + pos_inout);
1316                        int res = f4_processRecur(genot, genot_len, pos_inout, node);
1317                        if (res) return res;
1318                        if (pos_inout < genot_len)
1319                        {
1320                                res = f4_processRecur(genot, genot_len, pos_inout, node);
1321                                if (res) return res;
1322                        }
1323                        else // ran out
1324                        {
1325                                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?
1326                                //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):
1327                                //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.
1328                                // 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?
1329                        }
1330                        return 0;  // OK
1331                }
1332                case ' ':
1333                case '\n':
1334                case '\r':
1335                case '\t':
1336                {
1337                        // whitespace: ignore
1338                        pos_inout++;
1339                        break;
1340                }
1341                case 'N':
1342                {
1343                        int forgenorange = pos_inout;
1344                        if (genot[pos_inout + 1] != ':')
1345                                return pos_inout + 1; //error
1346                        pos_inout += 2; //skipping "N:"
1347                        unsigned int neuroclass_begin = pos_inout;
1348                        char* neuroclass_end = (char*)genot + neuroclass_begin;
1349                        NeuroClass *neuclass = GenoOperators::parseNeuroClass(neuroclass_end, ModelEnum::SHAPETYPE_BALL_AND_STICK); //advances neuroclass_end
1350                        if (neuclass == NULL)
1351                                return pos_inout + 1; //error
1352                        pos_inout += neuroclass_end - genot - neuroclass_begin;
1353                        string neutype = string(genot + neuroclass_begin, genot + pos_inout);
1354                        f4_Node *node = new f4_Node(neutype, par, forgenorange);
1355                        node->neuclass = neuclass;
1356                        par = node;
1357                        // if it continues with a colon that determines a neuron parameter (e.g. N:N:+=: ), then let the switch case for colon handle this
1358                        break;
1359                }
1360                case ':':
1361                {
1362                        // neuron parameter  +! -! += -= +/ or -/
1363                        // 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
1364                        char prop_dir, prop_symbol, prop_end[2]; // prop_end is only to ensure that neuron parameter definition is completed
1365                        if (sscanf(genot + pos_inout, ":%c%c%1[:]", &prop_dir, &prop_symbol, prop_end) != 3)
1366                                // error: incorrect format
1367                                return pos_inout + 1 + 1;
1368                        if (prop_dir != '-' && prop_dir != '+')
1369                                return pos_inout + 1 + 1; //error
1370                        switch (prop_symbol)
1371                        {
1372                        case '!':  case '=':  case '/':  break;
1373                        default:
1374                                return pos_inout + 1 + 1; //error
1375                        }
1376                        f4_Node *node = new f4_Node(":", par, pos_inout);
1377                        node->prop_symbol = prop_symbol;
1378                        node->prop_increase = prop_dir == '+' ? true : false; // + or -
1379                        par = node;
1380                        pos_inout += 4; //skipping :ds:
1381                        break;
1382                }
1383                case '[':
1384                {
1385                        double weight = 0;
1386                        int relfrom;
1387                        const char *end = parseConnection(genot + pos_inout, relfrom, weight);
1388                        if (end == NULL)
1389                                return pos_inout + 1; //error
1390
1391                        f4_Node *node = new f4_Node("[", par, pos_inout);
1392                        node->conn_from = relfrom;
1393                        node->conn_weight = weight;
1394                        par = node;
1395                        pos_inout += end - (genot + pos_inout);
1396                        break;
1397                }
1398                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
1399                {
1400                        //printf("any regular character '%c'\n", genot[pos_inout]);
1401#define F4_SIMPLIFY_MODIFIERS //avoid long, redundant sequences like ...<X>llmlIilImmimiimmimifmfl<fifmmimilimmmiimiliffmfliIfififlliflimfliffififmiffmflllfflimlififfiiffifIr<r<...
1402#ifdef F4_SIMPLIFY_MODIFIERS
1403                        char *ptr = (char*)(genot + pos_inout);
1404                        string original = "";
1405                        while (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
1406                        {
1407                                original += *ptr;
1408                                GenoOperators::skipWS(++ptr); //advance and ignore whitespace
1409                        }
1410                        int advanced = ptr - (genot + pos_inout);
1411                        if (advanced > 0) //found modifiers
1412                        {
1413                                string simplified = GenoOperators::simplifiedModifiers(original);
1414                                // add a node for each char in "simplified"
1415                                for (size_t i = 0; i < simplified.length(); i++)
1416                                {
1417                                        int pos = GenoOperators::strchrn0(genot + pos_inout, simplified[i]) - genot; //unnecessarily finding the same char, if it occurrs multiple times in simplified
1418                                        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)
1419                                        par = node;
1420                                }
1421                                pos_inout += advanced;
1422                        }
1423                        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
1424                        {
1425                                f4_Node *node = new f4_Node(genot[pos_inout], par, pos_inout);
1426                                par = node;
1427                                pos_inout++;
1428                        }
1429#else
1430                        f4_Node *node = new f4_Node(genot[pos_inout], par, pos_inout);
1431                        par = node;
1432                        pos_inout++;
1433#endif // F4_SIMPLIFY_MODIFIERS
1434                        break;
1435                }
1436                }
1437        }
1438
1439        // should end with a '>'
1440        if (par && par->name != ">")
1441        {
1442                //happens when pos_inout == genot_len
1443                //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>.
1444                new f4_Node('>', par, genot_len - 1);
1445        }
1446
1447        return 0;  // OK
1448}
1449
1450int f4_process(const char *genot, f4_Node *root)
1451{
1452        int pos = 0;
1453        int res = f4_processRecur(genot, (int)strlen(genot), pos, root);
1454        if (res > 0)
1455                return res; //parsing error
1456        else if (genot[pos] != 0)
1457                return pos + 1; //parsing OK but junk, unparsed genes left, for example /*4*/<X>N:N>whatever or /*4*/<X>X>>>
1458        else
1459                return 0; //parsing OK and parsed until the end
1460}
1461
1462const char* parseConnection(const char *fragm, int& relfrom, double &weight)
1463{
1464        const char *parser = fragm;
1465        if (*parser != '[') return NULL;
1466        parser++;
1467        ExtValue val;
1468        parser = val.parseNumber(parser, ExtPType::TInt);
1469        if (parser == NULL) return NULL;
1470        relfrom = val.getInt();
1471        if (*parser != ':') return NULL;
1472        parser++;
1473        parser = val.parseNumber(parser, ExtPType::TDouble);
1474        if (parser == NULL) return NULL;
1475        weight = val.getDouble();
1476        if (*parser != ']') return NULL;
1477        parser++;
1478        return parser;
1479}
1480
1481/*
1482f4_Node* f4_processTree(const char* geno)
1483{
1484        f4_Node *root = new f4_Node();
1485        int res = f4_processRecur(geno, 0, root);
1486        if (res) return NULL;
1487        //DB( printf("test f4  "); )
1488        DB(
1489                if (root->child)
1490                {
1491                        char* buf = (char*)malloc(300);
1492                        DB(printf("(%d) ", root->child->count());)
1493                                buf[0] = 0;
1494                        root->child->sprintAdj(buf);
1495                        DB(printf("%s\n", buf);)
1496                                free(buf);
1497                }
1498        )
1499                return root->child;
1500}
1501*/
Note: See TracBrowser for help on using the repository browser.