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

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