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

Last change on this file since 1230 was 1230, checked in by Maciej Komosinski, 12 months ago

Got rid of the (buggy) look-ahead function, made parsing stricter and simpler

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