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

Last change on this file was 1259, checked in by Maciej Komosinski, 10 months ago

f4: three #define's -> enum, minor refactorizations, added comments

  • Property svn:eol-style set to native
File size: 47.1 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5// Copyright (C) 1999,2000  Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL
6// 2018, Grzegorz Latosinski, added development checkpoints and support for new API for neuron types
7
8#include "f4_general.h"
9#include "../genooperators.h" // for GENOPER_ constants
10#include <common/nonstd_stl.h>
11#include <common/log.h>
12#include <frams/model/model.h> // for min and max attributes
13#include <common/nonstd_math.h>
14#include <algorithm> // std::min, std::max
15
16#ifdef DMALLOC
17#include <dmalloc.h>
18#endif
19
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
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 = f4_Cell_type::CELL_UNDIFF;
40        dadlink = ndad;
41        org = NULL;
42        genot = NULL;
43        gcur = old_gcur = NULL;
44        repeat.clear();
45        //genoRange.clear(); -- implicit
46
47        anglepos = nangle;
48        commacount = 0;
49        stickchildcount = 0;
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;
58        conns_count = 0;
59
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!)
64                if (ndad->type == f4_Cell_type::CELL_STICK)
65                {
66                        //firstend = ndad->lastend;
67                        //OM = ndad->OM;
68                        ndad->stickchildcount++;
69                }
70                if (ndad->type == f4_Cell_type::CELL_NEURON)
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));
79        P.muscle_bend_range = 1;
80}
81
82
83f4_Cell::f4_Cell(f4_Cells *nO, int nnr, f4_Node *ngeno, f4_Node *ngcur, f4_Cell *ndad, int nangle, GeneProps newP)
84{
85        nr = nnr;
86        type = f4_Cell_type::CELL_UNDIFF;
87        dadlink = ndad;
88        org = nO;
89        genot = ngeno;
90        gcur = old_gcur = ngcur;
91        repeat.clear();
92        //genoRange.clear(); -- implicit
93        // preserve geno range of parent cell
94        if (NULL != ndad)
95                genoRange.add(ndad->genoRange);
96
97        anglepos = nangle;
98        commacount = 0;
99        stickchildcount = 0;
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;
108        conns_count = 0;
109
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!)
114                if (ndad->type == f4_Cell_type::CELL_STICK)
115                {
116                        //firstend = ndad->lastend;
117                        //OM = ndad->OM;
118                        ndad->stickchildcount++;
119                }
120                if (ndad->type == f4_Cell_type::CELL_NEURON)
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));
129        P.muscle_bend_range = 1;
130}
131
132
133f4_Cell::~f4_Cell()
134{
135        // remove connections
136        if (conns_count)
137        {
138                int i;
139                for (i = conns_count - 1; i >= 0; i--)
140                        delete conns[i];
141                conns_count = 0;
142        }
143}
144
145void f4_Cell::oneStep()
146{
147        while (gcur != NULL)
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
152                //genoRange.add(gcur->pos,gcur->pos+gcur->name.length()-1);
153
154                // To detect what genes are valid neuroclass names, but do NOT have is_neuroclass==true
155                // (just as a curiosity to ensure we properly distinguish between, for example, the "G" neuron and the "G" modifier):
156                //char *TMP = (char*)gcur->name.c_str();
157                //if (gcur->is_neuroclass==false && GenoOperators::parseNeuroClass(TMP, ModelEnum::SHAPETYPE_BALL_AND_STICK))
158                //      printf("Could be a valid neuroclass, but is_neuroclass==false: %s\n", gcur->name.c_str());
159
160                if (gcur->neuclass == NULL) //not a neuron
161                {
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
165                        genoRange.add(gcur->pos, gcur->pos);
166                        char name = gcur->name[0];
167                        switch (name)
168                        {
169                        case '<':
170                        {
171                                // cell division!
172                                //DB( printf("  div! %d\n", name); )
173
174                                // error: sticks cannot divide
175                                if (type == f4_Cell_type::CELL_STICK)
176                                {
177                                        // cannot fix
178                                        org->setError(gcur->pos);
179                                        return;  // error code set -> stop further cells development
180                                }
181
182                                // undiff divides
183                                if (type == f4_Cell_type::CELL_UNDIFF)
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);
190                                        f4_Cell *tmp = new f4_Cell(org, org->cell_count, genot, gcur->child2, this, commacount, newP);
191                                        tmp->repeat = repeat;
192                                        repeat.clear();
193                                        org->addCell(tmp);
194                                }
195                                // a neuron divides: create a new, duplicate connections
196                                if (type == f4_Cell_type::CELL_NEURON)
197                                {
198                                        // daughter cell
199                                        f4_Cell *tmp = new f4_Cell(org, org->cell_count, genot, gcur->child2,
200                                                // has the same dadlink
201                                                this->dadlink, commacount, P);
202                                        tmp->repeat = repeat;
203                                        repeat.clear();
204                                        // it is a neuron from start
205                                        tmp->type = f4_Cell_type::CELL_NEURON;
206                                        // it has the same type as the parent neuron
207                                        tmp->neuclass = neuclass;
208                                        // duplicate connections
209                                        f4_CellConn *conn;
210                                        for (int i = 0; i < conns_count; i++)
211                                        {
212                                                conn = conns[i];
213                                                tmp->addConnection(conn->from, conn->weight);
214                                        }
215                                        org->addCell(tmp);
216                                }
217                                // adjustments for this cell
218                                gcur = gcur->child;
219                                return;  // error code not set -> halt this development and yield to other cells to develop
220                        }
221                        case '>':
222                        {
223                                // finish
224                                // see if there is a repeat count
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                                        }
243                                        else
244                                        {
245                                                repeat.pop();
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
254                                        }
255                                }
256                                else
257                                {
258                                        // error: still undiff
259                                        if (type == f4_Cell_type::CELL_UNDIFF)
260                                        {
261                                                // fix it: insert an 'X'
262                                                f4_Node *insertnode = new f4_Node("X", NULL, gcur->pos);
263                                                if (org->setRepairInsert(gcur->pos, gcur, insertnode)) // not in repair mode, release
264                                                        delete insertnode;
265                                                return;  // error code set -> stop further cells development
266                                        }
267                                        repeat.clear();
268                                        // eat up rest
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...
272                                        gcur = NULL;
273                                        return;  // done development
274                                }
275                        }
276#ifndef BREAK_WHEN_REP_COUNTER_NULL
277                        [[fallthrough]];
278#endif
279                        case '#':
280                        {
281                                // repetition marker
282                                if (repeat.top >= repeat_stack::stackSize)
283                                {
284                                        // repeat pointer stack is full, cannot remember this one.
285                                        // fix: delete it
286                                        org->setRepairRemove(gcur->pos, gcur);
287                                        return;  // error code set -> stop further cells development
288                                }
289                                repeat.push(repeat_ptr(gcur, gcur->reps));
290                                gcur = gcur->child;
291                                break;
292                        }
293                        case ',':
294                        {
295                                commacount++;
296                                gcur = gcur->child;
297                                break;
298                        }
299                        case 'r':
300                        case 'R':
301                        {
302                                // error: if neuron
303                                if (type == f4_Cell_type::CELL_NEURON)
304                                {
305                                        // fix: delete it
306                                        org->setRepairRemove(gcur->pos, gcur);
307                                        return;  // error code set -> stop further cells development
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;
316                        }
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':
331                        {
332                                // error: if neuron
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
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.
335                                        //printf("Handled as a modifier, but type==CELL_NEURON: '%c'\n", name);
336                                        // fix: delete it
337                                        org->setRepairRemove(gcur->pos, gcur);
338                                        return;  // error code set -> stop further cells development
339                                }
340                                P.executeModifier(name);
341                                gcur = gcur->child;
342                                break;
343                        }
344                        case 'X':
345                        {
346                                // turn undiff. cell into a stick
347                                // error: already differentiated
348                                if (type != f4_Cell_type::CELL_UNDIFF)
349                                {
350                                        // fix: delete this node
351                                        org->setRepairRemove(gcur->pos, gcur);
352                                        return;  // error code set -> stop further cells development
353                                }
354                                type = f4_Cell_type::CELL_STICK;
355                                // fix dad commacount and own anglepos
356                                if (dadlink != NULL)
357                                {
358                                        dadlink->commacount++;
359                                        anglepos = dadlink->commacount;
360                                }
361                                // change of type halts developments, see comment at 'neuclasshandler' below
362                                gcur = gcur->child;
363                                return;  // error code not set -> halt this development and yield to other cells to develop
364                        }
365                        case '[':
366                        {
367                                // connection to neuron
368                                // error: not a neuron
369                                if (type != f4_Cell_type::CELL_NEURON)
370                                {
371                                        // fix: delete it
372                                        org->setRepairRemove(gcur->pos, gcur);
373                                        return;  // error code set -> stop further cells development
374                                }
375                                // input [%d:%g]
376                                int relfrom = gcur->conn_from;
377                                double weight = gcur->conn_weight;
378                                f4_Cell *neu_from = NULL;
379
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++)
385                                {
386                                        if (org->C[i]->type == f4_Cell_type::CELL_NEURON) neu_counter++;
387                                        if (org->C[i] == this) { this_index = neu_counter - 1; break; }
388                                }
389                                // find index of incoming
390                                int from_index = this_index + relfrom;
391
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++)
399                                {
400                                        if (org->C[from]->type == f4_Cell_type::CELL_NEURON) neu_counter++;
401                                        if (from_index == (neu_counter - 1)) break;
402                                }
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))
409                                {
410                                        // cannot fix
411                                        org->setError(gcur->pos);
412                                        return;  // error code set -> stop further cells development
413                                }
414                                gcur = gcur->child;
415                                break;
416                        }
417                wait_conn:
418                        {
419                                // wait for other neurons to develop
420
421                                if (!org->development_stagnation) // other cells are developing, the situation is changing, we may continue waiting...
422                                        return;  // error code not set -> halt this development and yield to other cells to develop
423
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
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 == "#")
428                                {
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...
438
439                                        gcur = gcur->child;
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
441                                        return;  // error code not set -> halt this development and yield to other cells to develop
442                                }
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?)
447                                        return;  // error code set -> stop further cells development
448                                }
449#else
450                                // no more actives, cannot add connection, ignore, but treat not as an error - before 2023-04
451                                gcur = gcur->child;
452#endif
453                        }
454                        break;
455                        case ':':
456                        {
457                                // neuron parameter
458                                // error: not a neuron
459                                if (type != f4_Cell_type::CELL_NEURON)
460                                {
461                                        // fix: delete it
462                                        org->setRepairRemove(gcur->pos, gcur);
463                                        return;  // error code set -> stop further cells development
464                                }
465                                switch (gcur->prop_symbol)
466                                {
467                                case '!':
468                                        if (gcur->prop_increase)
469                                                force += (1.0 - force) * 0.2;
470                                        else
471                                                force -= force * 0.2;
472                                        break;
473                                case '=':
474                                        if (gcur->prop_increase)
475                                                inertia += (1.0 - inertia) * 0.2;
476                                        else
477                                                inertia -= inertia * 0.2;
478                                        break;
479                                case '/':
480                                        if (gcur->prop_increase)
481                                                sigmo *= 1.4;
482                                        else
483                                                sigmo /= 1.4;
484                                        break;
485                                default:
486                                        org->setRepairRemove(gcur->pos, gcur);
487                                        return;  // error code set -> stop further cells development
488                                }
489                                gcur = gcur->child;
490                                break;
491                        }
492                        case ' ':
493                        case '\t':
494                        case '\n':
495                        case '\r':
496                        {
497                                // whitespace has no effect, should not occur
498                                // fix: delete it
499                                org->setRepairRemove(gcur->pos, gcur);
500                                return;  // error code set -> stop further cells development
501                        }
502                        default:
503                        {
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);
508                                return;  // error code set -> stop further cells development
509                        }
510                        }
511                }
512                else
513                {
514                        genoRange.add(gcur->pos, gcur->pos + int(gcur->name.length()) + 2 - 1); // +2 for N:
515                        if (type != f4_Cell_type::CELL_UNDIFF)
516                        {
517                                // fix: delete this node
518                                org->setRepairRemove(gcur->pos, gcur);
519                                return;  // error code set -> stop further cells development
520                        }
521                        // error: if no previous
522                        if (dadlink == NULL)
523                        {
524                                // fix: delete it
525                                org->setRepairRemove(gcur->pos, gcur);
526                                return;  // error code set -> stop further cells development
527                        }
528                        neuclass = gcur->neuclass;
529                        type = f4_Cell_type::CELL_NEURON;
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
533                        gcur = gcur->child;
534                        return;  // error code not set -> halt this development and yield to other cells to develop
535                }
536        }
537}
538
539
540int f4_Cell::addConnection(f4_Cell *nfrom, double nweight)
541{
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++;
547        return 0;
548}
549
550
551void f4_Cell::adjustRecur()
552{
553        if (recurProcessedFlag)
554                // already processed
555                return;
556
557        // mark it processed
558        recurProcessedFlag = true;
559
560        // make sure its parent is processed first
561        if (dadlink != NULL)
562                dadlink->adjustRecur();
563
564        // count children
565        stickchildcount = 0;
566        for (int i = 0; i < org->cell_count; i++)
567        {
568                if (org->C[i]->dadlink == this)
569                        if (org->C[i]->type == f4_Cell_type::CELL_STICK)
570                                stickchildcount++;
571        }
572
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%
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.
578
579        if (type == f4_Cell_type::CELL_STICK)
580        {
581                if (dadlink == NULL)
582                {
583                        //firstend = Pt3D_0;
584                        // rotation due to rolling
585                        xrot = rolling;
586                }
587                else
588                {
589                        //firstend = dadlink->lastend;
590                        GeneProps Pdad = dadlink->P;
591                        GeneProps Padj = Pdad;
592                        Padj.propagateAlong(false);
593
594                        //f4_OrientMat rot = Orient_1;
595
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
603                                zrot = Padj.curvedness;
604                        }
605                        else
606                        {
607                                zrot = Padj.curvedness + (anglepos * 1.0 / (dadlink->commacount + 1) - 0.5) * M_PI * 2.0;
608                        }
609
610                        //rot = rot * f4_OrientMat(yOz, xrot);
611                        //rot = rot * f4_OrientMat(xOy, zrot);
612                        // rotation relative to parent stick
613                        //OM = rot * OM;
614
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        }
621}
622
623
624
625f4_CellConn::f4_CellConn(f4_Cell *nfrom, double nweight)
626{
627        from = nfrom;
628        weight = nweight;
629}
630
631
632
633f4_Cells::f4_Cells(f4_Node *genome, bool nrepair)
634{
635        repair = nrepair;
636        errorcode = GENOPER_OK;
637        errorpos = -1;
638        repair_remove = NULL;
639        repair_parent = NULL;
640        repair_insert = NULL;
641        tmpcel = NULL;
642
643        // create ancestor cell
644        C[0] = new f4_Cell(this, 0, genome, genome, NULL, 0, GeneProps::standard_values);
645        cell_count = 1;
646        development_stagnation = false;
647}
648
649
650
651f4_Cells::~f4_Cells()
652{
653        // release cells
654        if (cell_count)
655        {
656                for (int i = cell_count - 1; i >= 0; i--)
657                        delete C[i];
658                cell_count = 0;
659        }
660}
661
662
663bool f4_Cells::oneStep()
664{
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++)
667                C[i]->old_gcur = C[i]->gcur;
668
669        for (int i = 0; i < old_cell_count; i++)
670        {
671                C[i]->oneStep();
672                if (errorcode != GENOPER_OK)
673                        return false; // error -> end development
674        }
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
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        }
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
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.)
696        }
697}
698
699
700int f4_Cells::simulate()
701{
702        const bool PRINT_CELLS_DEVELOPMENT = false; //print the state of cells
703        errorcode = GENOPER_OK;
704        development_stagnation = false; //will be detected by oneStep()
705
706        if (PRINT_CELLS_DEVELOPMENT) f4_Node::print_tree(C[0]->genot, 0);
707        if (PRINT_CELLS_DEVELOPMENT) print_cells("Initialization");
708
709        // execute oneStep() in a cycle
710        while (oneStep()) if (PRINT_CELLS_DEVELOPMENT) print_cells("Development step");
711        if (PRINT_CELLS_DEVELOPMENT) print_cells("After last development step");
712
713        if (errorcode != GENOPER_OK) return errorcode;
714
715        // fix neuron attachements
716        for (int i = 0; i < cell_count; i++)
717        {
718                if (C[i]->type == f4_Cell_type::CELL_NEURON)
719                {
720                        while (C[i]->dadlink->type == f4_Cell_type::CELL_NEURON)
721                        {
722                                C[i]->dadlink = C[i]->dadlink->dadlink;
723                        }
724                }
725        }
726
727        // there should be no undiff. cells
728        // make undifferentiated cells sticks
729        for (int i = 0; i < cell_count; i++)
730        {
731                if (C[i]->type == f4_Cell_type::CELL_UNDIFF)
732                {
733                        C[i]->type = f4_Cell_type::CELL_STICK;
734                        //setError();
735                }
736        }
737
738        // recursive adjust
739        // reset recursive traverse flags
740        for (int i = 0; i < cell_count; i++)
741                C[i]->recurProcessedFlag = false;
742        // process every cell
743        for (int i = 0; i < cell_count; i++)
744                C[i]->adjustRecur();
745
746        //DB( printf("Cell simulation done, %d cells. \n", nc); )
747
748        if (PRINT_CELLS_DEVELOPMENT) print_cells("Final");
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);
752
753        return errorcode;
754}
755
756
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                {
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));
770                }
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.
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
783void f4_Cells::addCell(f4_Cell *newcell)
784{
785        if (cell_count >= F4_MAX_CELLS - 1)
786        {
787                delete newcell;
788                return;
789        }
790        C[cell_count] = newcell;
791        cell_count++;
792}
793
794
795void f4_Cells::setError(int nerrpos)
796{
797        errorcode = GENOPER_OPFAIL;
798        errorpos = nerrpos;
799}
800
801void f4_Cells::setRepairRemove(int nerrpos, f4_Node *to_remove)
802{
803        errorcode = GENOPER_REPAIR;
804        errorpos = nerrpos;
805        if (!repair)
806        {
807                // not in repair mode, treat as repairable error
808        }
809        else
810        {
811                repair_remove = to_remove;
812        }
813}
814
815int f4_Cells::setRepairInsert(int nerrpos, f4_Node *parent, f4_Node *to_insert)
816{
817        errorcode = GENOPER_REPAIR;
818        errorpos = nerrpos;
819        if (!repair)
820        {
821                // not in repair mode, treat as repairable error
822                return -1;
823        }
824        else
825        {
826                repair_parent = parent;
827                repair_insert = to_insert;
828                return 0;
829        }
830}
831
832void f4_Cells::repairGeno(f4_Node *geno, int whichchild)
833{
834        // assemble repaired geno, if the case
835        if (!repair) return;
836        if ((repair_remove == NULL) && (repair_insert == NULL)) return;
837        // traverse genotype tree, remove / insert node
838        f4_Node *g2;
839        if (whichchild == 1)
840                g2 = geno->child;
841        else
842                g2 = geno->child2;
843        if (g2 == NULL)
844                return;
845        if (g2 == repair_remove)
846        {
847                f4_Node *oldgeno;
848                geno->removeChild(g2);
849                if (g2->child)
850                {
851                        // add g2->child as child to geno
852                        if (whichchild == 1)
853                                geno->child = g2->child;
854                        else
855                                geno->child2 = g2->child;
856                        g2->child->parent = geno;
857                }
858                oldgeno = g2;
859                oldgeno->child = NULL;
860                delete oldgeno;
861                if (geno->child == NULL) return;
862                // check this new
863                repairGeno(geno, whichchild);
864                return;
865        }
866        if (g2 == repair_parent)
867        {
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);
878}
879
880
881void f4_Cells::toF1Geno(SString &out)
882{
883        if (tmpcel) delete tmpcel;
884        tmpcel = new f4_Cell(-1, NULL, 0, GeneProps::standard_values);
885        out = "";
886        toF1GenoRec(0, out);
887        delete tmpcel;
888}
889
890
891void f4_Cells::toF1GenoRec(int curc, SString &out)
892{
893        if (curc >= cell_count) return;
894
895        if (C[curc]->type != f4_Cell_type::CELL_STICK) return;
896
897        f4_Cell *thisti = C[curc];
898        if (thisti->dadlink != NULL)
899                *tmpcel = *(thisti->dadlink);
900
901        // Adjust length, curvedness, etc.
902        tmpcel->P.propagateAlong(false);
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.
909        while (tmpcel->P.length > thisti->P.length)
910        {
911                tmpcel->P.executeModifier('l');
912                out += "l";
913        }
914        while (tmpcel->P.length < thisti->P.length)
915        {
916                tmpcel->P.executeModifier('L');
917                out += "L";
918        }
919        while (tmpcel->P.curvedness > thisti->P.curvedness)
920        {
921                tmpcel->P.executeModifier('c');
922                out += "c";
923        }
924        while (tmpcel->P.curvedness < thisti->P.curvedness)
925        {
926                tmpcel->P.executeModifier('C');
927                out += "C";
928        }
929        while (thisti->rolling > 0.0f)
930        {
931                rolling_dec(&(thisti->rolling));
932                out += "R";
933        }
934        while (thisti->rolling < 0.0f)
935        {
936                rolling_inc(&(thisti->rolling));
937                out += "r";
938        }
939
940        // output X for this stick
941        out += "X";
942
943        // neurons attached to it
944        for (int i = 0; i < cell_count; i++)
945        {
946                if (C[i]->type == f4_Cell_type::CELL_NEURON)
947                {
948                        if (C[i]->dadlink == thisti)
949                        {
950                                f4_Cell *thneu = C[i];
951                                out += "[";
952                                out += thneu->neuclass->name.c_str();
953                                if (thneu->conns_count > 0)
954                                        out += ", ";
955                                // connections
956                                for (int j = 0; j < thneu->conns_count; j++)
957                                {
958                                        if (j > 0) out += ", ";
959                                        char buf[100];
960                                        sprintf(buf, "%d", thneu->conns[j]->from->nr - thneu->nr);
961                                        out += buf;
962                                        out += ":";
963                                        // connection weight
964                                        sprintf(buf, "%g", thneu->conns[j]->weight);
965                                        out += buf;
966                                }
967                                out += "]";
968                        }
969                }
970        }
971
972        // sticks connected to it
973        if (thisti->commacount >= 2)
974                out += "(";
975
976        int ccount = 1;
977        for (int i = 0; i < cell_count; i++)
978        {
979                if (C[i]->type == f4_Cell_type::CELL_STICK)
980                {
981                        if (C[i]->dadlink == thisti)
982                        {
983                                while (ccount < (C[i])->anglepos)
984                                {
985                                        ccount++;
986                                        out += ",";
987                                }
988                                toF1GenoRec(i, out);
989                        }
990                }
991        }
992
993        while (ccount < thisti->commacount)
994        {
995                ccount++;
996                out += ",";
997        }
998
999        if (thisti->commacount >= 2)
1000                out += ")";
1001}
1002
1003
1004
1005// to organize an f4 genotype in a tree structure
1006
1007f4_Node::f4_Node()
1008{
1009        name = "?";
1010        parent = NULL;
1011        child = NULL;
1012        child2 = NULL;
1013        pos = -1;
1014
1015        reps = 0;
1016        prop_symbol = '\0';
1017        prop_increase = false;
1018        conn_from = 0;
1019        conn_weight = 0.0;
1020        neuclass = NULL;
1021}
1022
1023f4_Node::f4_Node(string nname, f4_Node *nparent, int npos)
1024{
1025        name = nname;
1026        parent = nparent;
1027        child = NULL;
1028        child2 = NULL;
1029        pos = npos;
1030        if (parent) parent->addChild(this);
1031
1032        reps = 0;
1033        prop_symbol = '\0';
1034        prop_increase = false;
1035        conn_from = 0;
1036        conn_weight = 0.0;
1037        neuclass = NULL;
1038}
1039
1040f4_Node::f4_Node(char nname, f4_Node *nparent, int npos)
1041{
1042        name = nname;
1043        parent = nparent;
1044        child = NULL;
1045        child2 = NULL;
1046        pos = npos;
1047        if (parent) parent->addChild(this);
1048
1049        reps = 0;
1050        prop_symbol = '\0';
1051        prop_increase = false;
1052        conn_from = 0;
1053        conn_weight = 0.0;
1054        neuclass = NULL;
1055}
1056
1057f4_Node::~f4_Node()
1058{
1059        destroy();
1060}
1061
1062void f4_Node::print_tree(const f4_Node *root, int indent)
1063{
1064        for (int i = 0; i < indent; i++) printf(" ");
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);
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)
1076        {
1077                child = nchi;
1078                return 0;
1079        }
1080        if (child2 == NULL)
1081        {
1082                child2 = nchi;
1083                return 0;
1084        }
1085        return -1;
1086}
1087
1088int f4_Node::removeChild(f4_Node *nchi)
1089{
1090        if (nchi == child2)
1091        {
1092                child2 = NULL;
1093                return 0;
1094        }
1095        if (nchi == child)
1096        {
1097                child = NULL;
1098                return 0;
1099        }
1100        return -1;
1101}
1102
1103int f4_Node::childCount()
1104{
1105        return int(child != NULL) + int(child2 != NULL); //0, 1 or 2
1106}
1107
1108int f4_Node::count() const
1109{
1110        int c = 1;
1111        if (child != NULL)  c += child->count();
1112        if (child2 != NULL) c += child2->count();
1113        return c;
1114}
1115
1116f4_Node* f4_Node::ordNode(int n)
1117{
1118        int n1;
1119        if (n == 0) return this;
1120        n--;
1121        if (child != NULL)
1122        {
1123                n1 = child->count();
1124                if (n < n1) return child->ordNode(n);
1125                n -= n1;
1126        }
1127        if (child2 != NULL)
1128        {
1129                n1 = child2->count();
1130                if (n < n1) return child2->ordNode(n);
1131                n -= n1;
1132        }
1133        return NULL;
1134}
1135
1136f4_Node* f4_Node::randomNode()
1137{
1138        int n = count();
1139        // pick a random node between 0 and n-1
1140        return ordNode(rndUint(n));
1141}
1142
1143f4_Node* f4_Node::randomNodeWithSize(int mn, int mx)
1144{
1145        // try random nodes, and accept if size in range
1146        // limit to maxlim tries
1147        int i, n, maxlim;
1148        f4_Node *nod = NULL;
1149        maxlim = count();
1150        for (i = 0; i < maxlim; i++)
1151        {
1152                nod = randomNode();
1153                n = nod->count();
1154                if ((n >= mn) && (n <= mx)) return nod;
1155        }
1156        // failed, doesn't matter
1157        return nod;
1158}
1159
1160void f4_Node::sprint(SString& out)
1161{
1162        char buf2[20];
1163        // special case: repetition code
1164        if (name == "#")
1165        {
1166                out += "#";
1167                sprintf(buf2, "%d", reps);
1168                out += buf2;
1169        }
1170        else {
1171                // special case: neuron connection
1172                if (name == "[")
1173                {
1174                        out += "[";
1175                        sprintf(buf2, "%d", conn_from);
1176                        out += buf2;
1177                        sprintf(buf2, ":%g]", conn_weight);
1178                        out += buf2;
1179                }
1180                else if (name == ":")
1181                {
1182                        sprintf(buf2, ":%c%c:", prop_increase ? '+' : '-', prop_symbol);
1183                        out += buf2;
1184                }
1185                else if (neuclass != NULL)
1186                {
1187                        out += "N:";
1188                        out += neuclass->name.c_str();
1189                }
1190                else
1191                {
1192                        out += name.c_str();
1193                }
1194        }
1195
1196        if (child != NULL)
1197                child->sprint(out);
1198        // if two children, make sure last char is a '>'
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);
1205        // make sure last char is a '>'
1206        if (out[0] == 0) out += ">"; else
1207                if (out[out.length() - 1] != '>') out += ">";
1208}
1209
1210void f4_Node::sprintAdj(char *& buf)
1211{
1212        unsigned int len;
1213        // build in a SString, with initial size
1214        SString out;
1215        out.reserve(int(strlen(buf)) + 2000);
1216
1217        sprint(out);
1218        len = out.length();
1219
1220        // very last '>' can be omitted
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
1228        // copy back to string
1229        // if new is longer, reallocate buf
1230        if (len + 1 > strlen(buf))
1231        {
1232                buf = (char*)realloc(buf, len + 1);
1233        }
1234        strcpy(buf, out.c_str());
1235}
1236
1237f4_Node* f4_Node::duplicate()
1238{
1239        f4_Node *copy;
1240        copy = new f4_Node(*this);
1241        copy->parent = NULL;  // set later
1242        copy->child = NULL;
1243        copy->child2 = NULL;
1244        if (child != NULL)
1245        {
1246                copy->child = child->duplicate();
1247                copy->child->parent = copy;
1248        }
1249        if (child2 != NULL)
1250        {
1251                copy->child2 = child2->duplicate();
1252                copy->child2->parent = copy;
1253        }
1254        return copy;
1255}
1256
1257
1258void f4_Node::destroy()
1259{
1260        // children are destroyed (recursively) through the destructor
1261        if (child2 != NULL) delete child2;
1262        if (child != NULL) delete child;
1263}
1264
1265// scan genotype string and build a tree
1266// return >1 for error (errorpos)
1267int f4_processRecur(const char* genot, const int genot_len, int &pos_inout, f4_Node *parent)
1268{
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
1273        f4_Node *par = parent;
1274
1275        if (pos_inout >= genot_len)
1276                return genot_len + 1;
1277
1278        while (pos_inout < genot_len)
1279        {
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                }
1287                switch (genot[pos_inout])
1288                {
1289                case '<':
1290                {
1291                        f4_Node *node = new f4_Node("<", par, pos_inout);
1292                        par = node;
1293                        pos_inout++; //move after '<'
1294                        int res = f4_processRecur(genot, genot_len, pos_inout, par);
1295                        if (res) return res;
1296                        if (pos_inout < genot_len)
1297                        {
1298                                res = f4_processRecur(genot, genot_len, pos_inout, par);
1299                                if (res) return res;
1300                        }
1301                        else // ran out
1302                        {
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>
1304                                return genot_len + 1;
1305                                //old silent repair:
1306                                //node = new f4_Node(">", par, genot_len - 1);
1307                        }
1308                        return 0;  // OK
1309                }
1310                case '>':
1311                {
1312                        new f4_Node(">", par, pos_inout);
1313                        pos_inout++; //move after '>'
1314                        return 0;  // OK
1315                }
1316                case '#':
1317                {
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
1323                        f4_Node *node = new f4_Node("#", par, pos_inout); //TODO here or elsewhere: gene mapping seems to map '#' but not the following number
1324                        node->reps = reps.getInt();
1325                        // skip number
1326                        pos_inout += end - (genot + pos_inout);
1327                        int res = f4_processRecur(genot, genot_len, pos_inout, node);
1328                        if (res) return res;
1329                        if (pos_inout < genot_len)
1330                        {
1331                                res = f4_processRecur(genot, genot_len, pos_inout, node);
1332                                if (res) return res;
1333                        }
1334                        else // ran out
1335                        {
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?
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?
1340                        }
1341                        return 0;  // OK
1342                }
1343                case ' ':
1344                case '\n':
1345                case '\r':
1346                case '\t':
1347                {
1348                        // whitespace: ignore
1349                        pos_inout++;
1350                        break;
1351                }
1352                case 'N':
1353                {
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
1361                        if (neuclass == NULL)
1362                                return pos_inout + 1; //error
1363                        pos_inout += neuroclass_end - genot - neuroclass_begin;
1364                        string neutype = string(genot + neuroclass_begin, genot + pos_inout);
1365                        f4_Node *node = new f4_Node(neutype, par, forgenorange);
1366                        node->neuclass = neuclass;
1367                        par = node;
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
1369                        break;
1370                }
1371                case ':':
1372                {
1373                        // neuron parameter  +! -! += -= +/ or -/
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
1376                        if (sscanf(genot + pos_inout, ":%c%c%1[:]", &prop_dir, &prop_symbol, prop_end) != 3)
1377                                // error: incorrect format
1378                                return pos_inout + 1 + 1;
1379                        if (prop_dir != '-' && prop_dir != '+')
1380                                return pos_inout + 1 + 1; //error
1381                        switch (prop_symbol)
1382                        {
1383                        case '!':  case '=':  case '/':  break;
1384                        default:
1385                                return pos_inout + 1 + 1; //error
1386                        }
1387                        f4_Node *node = new f4_Node(":", par, pos_inout);
1388                        node->prop_symbol = prop_symbol;
1389                        node->prop_increase = prop_dir == '+' ? true : false; // + or -
1390                        par = node;
1391                        pos_inout += 4; //skipping :ds:
1392                        break;
1393                }
1394                case '[':
1395                {
1396                        double weight = 0;
1397                        int relfrom;
1398                        const char *end = parseConnection(genot + pos_inout, relfrom, weight);
1399                        if (end == NULL)
1400                                return pos_inout + 1; //error
1401
1402                        f4_Node *node = new f4_Node("[", par, pos_inout);
1403                        node->conn_from = relfrom;
1404                        node->conn_weight = weight;
1405                        par = node;
1406                        pos_inout += end - (genot + pos_inout);
1407                        break;
1408                }
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
1410                {
1411                        //printf("any regular character '%c'\n", genot[pos_inout]);
1412#define F4_SIMPLIFY_MODIFIERS //avoid long, redundant sequences like ...<X>llmlIilImmimiimmimifmfl<fifmmimilimmmiimiliffmfliIfififlliflimfliffififmiffmflllfflimlififfiiffifIr<r<...
1413#ifdef F4_SIMPLIFY_MODIFIERS
1414                        char *ptr = (char*)(genot + pos_inout);
1415                        string original = "";
1416                        while (GenoOperators::strchrn0(all_modifiers_no_comma, *ptr)) //only processes a section of chars known in all_modifiers_no_comma, other characters will exit the loop
1417                        {
1418                                original += *ptr;
1419                                GenoOperators::skipWS(++ptr); //advance and ignore whitespace
1420                        }
1421                        int advanced = ptr - (genot + pos_inout);
1422                        if (advanced > 0) //found modifiers
1423                        {
1424                                string simplified = GenoOperators::simplifiedModifiers(original);
1425                                // add a node for each char in "simplified"
1426                                for (size_t i = 0; i < simplified.length(); i++)
1427                                {
1428                                        int pos = GenoOperators::strchrn0(genot + pos_inout, simplified[i]) - genot; //unnecessarily finding the same char, if it occurrs multiple times in simplified
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
1441                        f4_Node *node = new f4_Node(genot[pos_inout], par, pos_inout);
1442                        par = node;
1443                        pos_inout++;
1444#endif // F4_SIMPLIFY_MODIFIERS
1445                        break;
1446                }
1447                }
1448        }
1449
1450        // should end with a '>'
1451        if (par && par->name != ">")
1452        {
1453                //happens when pos_inout == genot_len
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>.
1455                new f4_Node('>', par, genot_len - 1);
1456        }
1457
1458        return 0;  // OK
1459}
1460
1461int f4_process(const char *genot, f4_Node *root)
1462{
1463        int pos = 0;
1464        int res = f4_processRecur(genot, (int)strlen(genot), pos, root);
1465        if (res > 0)
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
1471}
1472
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}
1491
1492/*
1493f4_Node* f4_processTree(const char* geno)
1494{
1495        f4_Node *root = new f4_Node();
1496        int res = f4_processRecur(geno, 0, root);
1497        if (res) return NULL;
1498        //DB( printf("test f4  "); )
1499        DB(
1500                if (root->child)
1501                {
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);
1508                }
1509        )
1510                return root->child;
1511}
1512*/
Note: See TracBrowser for help on using the repository browser.