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

Last change on this file since 1314 was 1313, checked in by Maciej Komosinski, 4 months ago

Color mutations in f1 and f4, and a new syntax for "allowed modifiers" (opposite to previous "excluded modifiers") with optional probabilities for each modifier

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