source: cpp/frams/genetics/fL/fL_general.cpp @ 1279

Last change on this file since 1279 was 1273, checked in by Maciej Komosinski, 15 months ago

fH, fB, fL: improved default parameter values, syntax coloring and code logic

File size: 36.4 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#include <algorithm>
6#include <stack>
7#include "fL_general.h"
8#include <frams/util/multirange.h>
9#include <iterator>
10
11const char *fL_part_names[FL_PART_PROPS_COUNT] = { "fr" }; // "dn", "ing", "as" };
12const char *fL_part_fullnames[FL_PART_PROPS_COUNT] = { "friction" }; // "density", "ingestion", "assimilation" };
13
14const char *fL_joint_names[FL_JOINT_PROPS_COUNT] = { "rotstif" }; //"stif", , "stam" }; //see the comment to fH_joint_names in fH_general.cpp
15const char *fL_joint_fullnames[FL_JOINT_PROPS_COUNT] = { "rotation stiffness" }; //"stiffness", "stamina" };
16
17#define FIELDSTRUCT fL_Word
18ParamEntry fL_word_paramtab[] =
19{
20        { "Word", 1, 2, "w" },
21        { "name", 0, PARAM_CANOMITNAME, "word name", "s", FIELD(name), },
22        { "npar", 0, PARAM_CANOMITNAME, "number of parameters", "d 0 " FL_MAXPARAMS " 0", FIELD(npar), },
23        { 0, 0, 0, }
24};
25#undef FIELDSTRUCT
26
27#define FIELDSTRUCT fL_Rule
28ParamEntry fL_rule_paramtab[] =
29{
30        { "Rule", 1, 3, "r" },
31        { "pred", 0, 0, "predecessor", "s", FIELD(predecessor), },
32        { "cond", 0, 0, "parameter condition", "s", FIELD(condition), },
33        { "succ", 0, 0, "successor", "s", FIELD(successor), },
34        { 0, 0, 0, }
35};
36#undef FIELDSTRUCT
37
38#define FIELDSTRUCT fL_Builder
39ParamEntry fL_builder_paramtab[] =
40{
41                {"LSystemInfo", 1, 4, "i"},
42                {"axiom", 0, 0, "starting sequence of L-System", "s", FIELD(axiom),},
43                {"time", 0, PARAM_CANOMITNAME, "development time", "f 0.0 " FL_MAXITER " 1.0", FIELD(time),},
44                {"numckp", 0, PARAM_CANOMITNAME, "number of checkpoints", "d 1 50 1", FIELD(numckp),},
45                {"maxwords", 0, PARAM_CANOMITNAME, "Maximum number of words within genotype sequence", "d -1 9999 -1", FIELD(maxwords),},
46                {0,0,0,}
47};
48#undef FIELDSTRUCT
49
50fL_Builder::~fL_Builder()
51{
52        // first remove words from builder
53        for (fL_Word *word : genotype)
54        {
55                delete word;
56        }
57        genotype.clear();
58        // remove rules from builder
59        for (fL_Rule *rule : rules)
60        {
61                delete rule;
62        }
63        rules.clear();
64
65        // remove words definitions with their ParamTabs
66        std::unordered_map<std::string, fL_Word *>::iterator it;
67        for (it = words.begin(); it != words.end(); it++)
68        {
69                ParamObject::freeParamTab(it->second->tab);
70                delete it->second;
71        }
72        words.clear();
73}
74
75bool fL_Builder::getNextObject(int &pos, const SString &src, SString &token)
76{
77        // if position exceeds length then return false
78        if (pos >= src.length()) return false;
79        int opencount = -1;
80        int i = pos;
81        for (; i < src.length(); i++)
82        {
83                // token cannot contain branching parenthesis
84                if (src[i] == '[' || src[i] == ']')
85                {
86                        // if token started - return parenthesis mismatch
87                        if (opencount != -1)
88                        {
89                                pos = -1;
90                                return false;
91                        }
92                        // otherwise [ and ] are interpreted as tokens and they do not have parenthesis
93                        token = src.substr(pos, i + 1 - pos);
94                        pos = i + 1;
95                        return true;
96                }
97                // every word, except [ and ], has () parenthesis
98                // every open parenthesis increment opencount counter;
99                if (src[i] == '(')
100                {
101                        if (opencount == -1) opencount = 1;
102                        else opencount++;
103                }
104                // every close parenthesis decrement opencount counter
105                else if (src[i] == ')')
106                {
107                        // if there were no open parenthesis, return parenthesis mismatch
108                        if (opencount == -1)
109                        {
110                                pos = -1;
111                                return false;
112                        }
113                        else opencount--;
114                }
115                // if counter reaches 0, the token extraction is finished
116                if (opencount == 0)
117                {
118                        break;
119                }
120        }
121        if (opencount == 0)
122        {
123                token = src.substr(pos, i + 1 - pos);
124                pos = i + 1;
125                return true;
126        }
127        // if there was no closing parenthesis, then return parenthesis mismatch
128        pos = -1;
129        return false;
130}
131
132std::string fL_Builder::trimSpaces(const std::string& data)
133{
134        size_t first = data.find_first_not_of(' ');
135        if (std::string::npos == first)
136        {
137                return data;
138        }
139        size_t last = data.find_last_not_of(' ');
140        return data.substr(first, (last - first + 1));
141}
142
143int fL_Builder::createWord(const SString &token, fL_Word *&word, int numparams, int begin, int end)
144{
145        SString wordn;
146        int tokpos = 0;
147        // if word name cannot be extracted, then return error
148        if (!token.getNextToken(tokpos, wordn, '('))
149        {
150                return 1;
151        }
152        std::string wordname = fL_Builder::trimSpaces(wordn.c_str());
153        // if word cannot be found in available words, then return error
154        if (words.find(wordname) == words.end())
155        {
156                SString message = "Word '";
157                message += wordname.c_str();
158                message += "' in sequence does not exist";
159                logMessage("fL_Builder", "createWord", LOG_ERROR, message.c_str());
160                return 1;
161        }
162
163        if (word) delete word;
164        // create new word and assign parameters
165        word = new fL_Word(false, begin, end);
166
167        *word = *words[wordname];
168
169        SString temp;
170        temp = token.substr(tokpos);
171        temp = temp.substr(0, temp.length() - 1);
172
173        // if word has parameters
174        if (word->npar > 0)
175        {
176                // create ParamObject that will hold parameter data
177                word->data = ParamObject::makeObject(word->tab);
178                Param par(word->tab);
179                par.select(word->data);
180                par.setDefault();
181                ParamInterface::LoadOptions opts;
182
183                // load parameters from string
184                par.load(ParamInterface::FormatSingleLine, temp, &opts);
185                for (int i = 0; i < par.getPropCount(); i++)
186                {
187                        SString t(par.id(i));
188                        if (word->builtin && (t == SString("d") || t == SString(FL_PE_CONN_ATTR)))
189                        {
190                                word->parevals.push_back(NULL);
191                        }
192                        else
193                        {
194                                // create MathEvaluation object to check if string contained by
195                                // parameter is valid
196                                double tmp;
197                                MathEvaluation *eval = NULL;
198                                SString seq = par.getString(i);
199                                // if string is empty, then evaluate this with 0
200                                // if sequence could not be evaluated, then return error
201                                if (seq.length() > 0)
202                                {
203                                        eval = new MathEvaluation(numparams);
204                                        if (eval->evaluate(seq.c_str(), tmp) != 0)
205                                        {
206                                                SString message = "Word in sequence has invalid parameter:  ";
207                                                message += temp;
208                                                logMessage("fL_Builder", "createWord", LOG_ERROR, message.c_str());
209                                                delete eval;
210                                                delete word;
211                                                word = NULL;
212                                                return 1;
213                                        }
214                                }
215                                word->parevals.push_back(eval);
216                        }
217                }
218        }
219        else if (word->npar == 0 && temp.length() > 0)
220        {
221                SString message = "Too many parameters for word:  ";
222                message += token;
223                logMessage("fL_Builder", "createWord", LOG_ERROR, message.c_str());
224                delete word;
225                word = NULL;
226                return 1;
227        }
228        return 0;
229}
230
231int fL_Builder::tokenize(const SString &sequence, std::list<fL_Word *> &result, int numparams, int begin, int end)
232{
233        int pos = 0;
234        SString token;
235        int branchcount = 0;
236        if (result.size() > 0)
237        {
238                for (fL_Word *word : result)
239                {
240                        delete word;
241                }
242                result.clear();
243        }
244        // iterate through available tokens
245        while (getNextObject(pos, sequence, token))
246        {
247                // if token is of open branch type, then add start of branch
248                if (token.indexOf("[", 0) != -1)
249                {
250                        fL_Branch *word = new fL_Branch(fL_Branch::BranchType::OPEN, begin, end);
251                        result.push_back(word);
252                        branchcount++;
253                        continue;
254                }
255                // if token is of closed branch type, then add end of branch
256                if (token.indexOf("]", 0) != -1)
257                {
258                        if (branchcount == 0)
259                        {
260                                SString message = "Branch parenthesis mismatch at:  ";
261                                message += sequence;
262                                logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str());
263                                return 1;
264                        }
265                        fL_Branch *word = new fL_Branch(fL_Branch::BranchType::CLOSE, begin, end);
266                        result.push_back(word);
267                        branchcount--;
268                        continue;
269                }
270                fL_Word *word = NULL;
271                if (createWord(token, word, numparams, begin, end) != 0)
272                {
273                        SString message = "Error during parsing words sequence:  ";
274                        message += sequence;
275                        logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str());
276                        return 1;
277                }
278                if (word->name == "C")
279                {
280                        Param par(word->tab, word->data);
281                        SString attr = par.getStringById(FL_PE_CONN_ATTR);
282                        if (attr.indexOf("$t", 0) != -1)
283                        {
284                                logMessage("fL_Builder", "tokenize", LOG_ERROR, "Attractor definition cannot contain time variable");
285                                delete word;
286                                return 1;
287
288                        }
289                        if (attr != "")
290                        {
291                                fL_Word *attrword = NULL;
292                                if (createWord(attr, attrword, numparams, begin, end) != 0)
293                                {
294                                        SString message = "Error during parsing attractor word:  ";
295                                        message += attr;
296                                        logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str());
297                                        delete word;
298                                        if (attrword) delete attrword;
299                                        return 1;
300                                }
301                                if (attrword->builtin)
302                                {
303                                        logMessage("fL_Builder", "tokenize", LOG_ERROR, "Attractor words cannot be built-in");
304                                        delete word;
305                                        delete attrword;
306                                        return 1;
307                                }
308                                delete attrword;
309                        }
310                }
311                result.push_back(word);
312        }
313
314        // check if there were no parenthesis errors in genotype
315        if (pos == -1)
316        {
317                SString message = "Parenthesis mismatch at:  ";
318                message += sequence;
319                logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str());
320                return 1;
321        }
322        if (branchcount != 0)
323        {
324                SString message = "Branching mismatch at:  ";
325                message += sequence;
326                logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str());
327                return 1;
328        }
329        return 0;
330}
331
332void fL_Word::operator=(const fL_Word& src)
333{
334        if (&src != this)
335        {
336                name = src.name;
337                npar = src.npar;
338
339                //mut = src.mut;
340                tab = src.tab;
341
342                parevals = src.parevals;
343
344                builtin = src.builtin;
345
346                data = NULL; // properties cannot be copied
347        }
348}
349
350int fL_Word::processDefinition(fL_Builder *builder)
351{
352        // if word already exist, then return error
353        if (this->name.length() == 0)
354        {
355                logMessage("fL_Word", "processDefinition", LOG_ERROR, "Axiom name is empty");
356                return 1;
357        }
358        if (builder->words.find(this->name.c_str()) != builder->words.end())
359        {
360                std::string message = "Word redefinition:  ";
361                message += this->name.c_str();
362                logMessage("fL_Word", "processDefinition", LOG_ERROR, message.c_str());
363                return 1;
364        }
365
366        // create ParamTab for word
367        for (int i = 0; i < npar; i++)
368        {
369                std::string n = "n";
370                n += std::to_string(i);
371                mut.addProperty(NULL, n.c_str(), LSYSTEM_PARAM_TYPE, n.c_str(), "", PARAM_CANOMITNAME, 0, -1);
372        }
373
374        tab = ParamObject::makeParamTab((ParamInterface *)&mut, 0, 0, mut.firstMutableIndex());
375
376        builder->words[this->name.c_str()] = this;
377        builder->wordnames.push_back(this->name.c_str());
378        return 0;
379}
380
381int fL_Rule::processDefinition(fL_Builder *builder)
382{
383        // if there is no word among words that matches predecessor, then return error
384        if (builder->words.find(predecessor.c_str()) == builder->words.end())
385        {
386                logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Word in Rule condition does not exist");
387                return 1;
388        }
389
390        objpred = new fL_Word();
391        *objpred = *builder->words[predecessor.c_str()];
392
393        if (objpred->builtin)
394        {
395                logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Builtin words cannot be predecessors");
396                return 1;
397        }
398
399        // parse condition
400        if (condition != "")
401        {
402                if (objpred->builtin && (objpred->name == "N" || objpred->name == "C"))
403                {
404                        logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Rules with neuron/connection word predecessors cannot contain conditions");
405                        return 1;
406                }
407                std::string cond = condition.c_str();
408                condeval = new MathEvaluation(objpred->npar);
409                double tmp;
410                if (condeval->evaluate(condition.c_str(), tmp) != 0)
411                {
412                        SString message = "Parametric condition of rule invalid:  ";
413                        message += condition;
414                        logMessage("fL_Rule", "processDefinition", LOG_ERROR, message.c_str());
415                        return 1;
416                }
417        }
418
419        // parse successor
420        if (successor == "")
421        {
422                logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Successor cannot be empty");
423                return 1;
424        }
425
426        if (builder->tokenize(successor, objsucc, objpred->npar, begin, end) != 0)
427        {
428                logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Unable to process successor sequence");
429                return 1;
430        }
431
432        builder->rules.push_back(this);
433        return 0;
434}
435
436int fL_Builder::processDefinition(fL_Builder *builder)
437{
438        // tokenize axiom
439        if (tokenize(axiom, genotype, 0, begin, end) != 0)
440        {
441                logMessage("fL_Builder", "processDefinition", LOG_ERROR, "Unable to process axiom sequence");
442                return 1;
443        }
444        else if (genotype.size() == 0)
445        {
446                logMessage("fL_Builder", "processDefinition", LOG_ERROR, "Axiom sequence is empty");
447                return 1;
448        }
449        return 0;
450}
451
452int fL_Builder::processLine(fLElementType type, const SString &line, fL_Element *&obj, int linenumber, int begin, int end)
453{
454        ParamEntry *tab;
455        // choose proper ParamTab and construct proper object
456        switch (type)
457        {
458        case fLElementType::TERM:
459        {
460                tab = fL_word_paramtab;
461                obj = new fL_Word();
462                break;
463        }
464        case fLElementType::INFO:
465        {
466                tab = fL_builder_paramtab;
467                obj = this;
468                break;
469        }
470        case fLElementType::RULE:
471        {
472                tab = fL_rule_paramtab;
473                obj = new fL_Rule(begin, end);
474                break;
475        }
476        default:
477                tab = NULL;
478                obj = NULL;
479                break;
480        }
481        Param par(tab);
482        par.select(obj);
483        par.setDefault();
484        ParamInterface::LoadOptions opts;
485
486        par.load(ParamInterface::FormatSingleLine, line, &opts);
487
488        if (opts.parse_failed)
489        {
490                std::string message = "Error in parsing parameters at line:  " + std::to_string(linenumber);
491                logMessage("fL_Builder", "processLine", LOG_ERROR, message.c_str());
492                if (obj != this) delete obj;
493                return begin + 1;
494        }
495
496        return 0;
497}
498
499void fL_Builder::addModelWords()
500{
501        // stick S
502        fL_Word *stick = new fL_Word(true);
503        stick->name = "S";
504        stick->npar = FL_PART_PROPS_COUNT + FL_JOINT_PROPS_COUNT + 1; //MacKo 2023-07: was hardcoded "8" but crashed when the number of props in Part and Joint was decreased; I think it should be like it is now (4+3+1 - or another value depending on prop counts)
505        for (int i = 0; i < FL_PART_PROPS_COUNT; i++)
506        {
507                stick->mut.addProperty(NULL, fL_part_names[i], "s", fL_part_fullnames[i], fL_part_fullnames[i], PARAM_CANOMITNAME, 0, -1);
508        }
509        for (int i = 0; i < FL_JOINT_PROPS_COUNT; i++)
510        {
511                stick->mut.addProperty(NULL, fL_joint_names[i], "s", fL_joint_fullnames[i], fL_joint_fullnames[i], PARAM_CANOMITNAME, 0, -1);
512        }
513
514        stick->mut.addProperty(NULL, "l", "s", "length", "length", PARAM_CANOMITNAME, 0, -1);
515        stick->tab = ParamObject::makeParamTab((ParamInterface *)&stick->mut, 0, 0, stick->mut.firstMutableIndex());
516        words["S"] = stick;
517        wordnames.push_back("S");
518
519        // neuron N
520        fL_Word *neuron = new fL_Word(true);
521        neuron->name = "N";
522        neuron->npar = 1;
523        neuron->mut.addProperty(NULL, "d", "s", "details", "details", 0, 0, -1);
524        neuron->tab = ParamObject::makeParamTab((ParamInterface *)&neuron->mut, 0, 0, neuron->mut.firstMutableIndex());
525        words["N"] = neuron;
526        wordnames.push_back("N");
527
528        // connection C
529        fL_Word *connection = new fL_Word(true);
530        connection->name = "C";
531        connection->npar = 2;
532        connection->mut.addProperty(NULL, FL_PE_CONN_WEIGHT, "s", "weight", "weight", PARAM_CANOMITNAME, 0, -1);
533        connection->mut.addProperty(NULL, FL_PE_CONN_ATTR, "s", "attractor", "connection attractor", PARAM_CANOMITNAME, 0, -1);
534        connection->tab = ParamObject::makeParamTab((ParamInterface *)&connection->mut, 0, 0, connection->mut.firstMutableIndex());
535        words["C"] = connection;
536        wordnames.push_back("C");
537
538        // rotation objects
539        fL_Word *rotx = new fL_Word(true);
540        rotx->name = "rotX";
541        rotx->npar = 1;
542        rotx->processDefinition(this);
543
544        fL_Word *roty = new fL_Word(true);
545        roty->name = "rotY";
546        roty->npar = 1;
547        roty->processDefinition(this);
548
549        fL_Word *rotz = new fL_Word(true);
550        rotz->name = "rotZ";
551        rotz->npar = 1;
552        rotz->processDefinition(this);
553
554        //fL_Branch *branch = new fL_Branch(fL_Branch::BranchType::OPEN, 0, 0);
555        //branch->processDefinition(this);
556
557        builtincount = words.size();
558}
559
560int fL_Builder::parseGenotype(const SString &genotype)
561{
562        int pos = 0;
563        int lastpos = 0;
564        SString line;
565        int linenumber = 0;
566
567        fLElementType type = fLElementType::TERM;
568
569        // add default words first to prevent redefinitions
570        addModelWords();
571
572        while (genotype.getNextToken(pos, line, '\n'))
573        {
574                if (line.length() > 0)
575                {
576                        // words can be defined in the beginning of genotype
577                        if (line.startsWith("w:") && type != fLElementType::TERM)
578                        {
579                                logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "All words should be defined in the beginning of genotype");
580                                return lastpos + 1;
581                        }
582                        else if (line.startsWith("i:"))
583                        {
584                                // after all words are defined, next definition should be information
585                                if (type == fLElementType::TERM)
586                                {
587                                        type = fLElementType::INFO;
588                                }
589                                else
590                                {
591                                        logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "Axioms and iteration number should be defined after word definitions");
592                                        return lastpos + 1;
593                                }
594                        }
595                        else if (line.startsWith("r:"))
596                        {
597                                // after information definition, the last thing is rule definitions
598                                if (type == fLElementType::TERM)
599                                {
600                                        logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "Axiom is not defined - define it after words definition");
601                                        return lastpos + 1;
602                                }
603                                else if (type == fLElementType::INFO)
604                                {
605                                        type = fLElementType::RULE;
606                                }
607                        }
608                        // create object
609                        fL_Element *obj = NULL;
610                        int res = processLine(type, line.substr(2), obj, linenumber, lastpos, pos - 1);
611                        if (res != 0)
612                        {
613                                if (obj && obj != this) delete obj;
614                                return res;
615                        }
616                        if (obj == this)
617                        {
618                                begin = lastpos;
619                                end = pos - 1;
620                        }
621                        res = obj->processDefinition(this);
622                        if (res != 0)
623                        {
624                                if (obj && obj != this) delete obj;
625                                return res;
626                        }
627                }
628                lastpos = pos;
629        }
630        if (type == fLElementType::TERM)
631        {
632                logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "Info line was not declared");
633                return 1;
634        }
635        return 0;
636}
637
638int fL_Word::saveEvals(bool keepformulas)
639{
640        if (npar > 0)
641        {
642                Param par(tab);
643                par.select(data);
644                for (int i = 0; i < npar; i++)
645                {
646                        SString t(par.id(i));
647                        if (parevals[i] != NULL)
648                        {
649                                double val;
650                                if (parevals[i]->evaluateRPN(val) != 0)
651                                {
652                                        logMessage("fL_Word", "saveEvals", LOG_ERROR, "Could not stringify mathematical expression in Word");
653                                        return 1;
654                                }
655                                if (val == 0)
656                                {
657                                        par.setString(i, "");
658                                }
659                                else
660                                {
661                                        if (keepformulas)
662                                        {
663                                                std::string res;
664                                                if (parevals[i]->RPNToInfix(res) != 0)
665                                                {
666                                                        logMessage("fL_Word", "saveEvals", LOG_ERROR, "Could not stringify mathematical expression in Word");
667                                                        return 1;
668                                                }
669                                                par.setString(i, res.c_str());
670                                        }
671                                        else
672                                        {
673                                                SString r = SString::valueOf(val);
674                                                par.setString(i, r);
675                                        }
676                                }
677                        }
678                }
679        }
680        return 0;
681}
682
683// Methods for converting L-System objects to string
684
685SString fL_Word::toString()
686{
687        Param par(fL_word_paramtab);
688        fL_Word *obj = new fL_Word();
689        par.select(this);
690        SString res;
691        par.saveSingleLine(res, obj, true, false);
692        res = SString("w:") + res;
693        delete obj;
694        return res;
695}
696
697SString fL_Word::stringify(bool keepformulas)
698{
699        SString res = name;
700        SString params = "";
701        if (npar > 0)
702        {
703                saveEvals(keepformulas);
704                Param par(tab);
705                void *obj = ParamObject::makeObject(tab);
706                par.select(obj);
707                par.setDefault();
708                par.select(data);
709                par.saveSingleLine(params, obj, false, false);
710                ParamObject::freeObject(obj);
711        }
712        res += "(";
713        res += params + ")";
714        return res;
715}
716
717SString fL_Rule::toString()
718{
719        predecessor = objpred->name;
720        std::string tmp;
721        if (condeval)
722        {
723                condeval->RPNToInfix(tmp);
724                condition = tmp.c_str();
725        }
726        else
727        {
728                condition = "";
729        }
730        successor = "";
731        std::list<fL_Word *>::iterator i;
732        for (i = objsucc.begin(); i != objsucc.end(); i++)
733        {
734                successor += (*i)->stringify();
735        }
736        Param par(fL_rule_paramtab);
737        fL_Rule *obj = new fL_Rule(0, 0);
738        par.select(this);
739        SString res;
740        par.saveSingleLine(res, obj, true, false);
741        res = SString("r:") + res;
742        delete obj;
743        return res;
744}
745
746SString fL_Builder::getStringifiedProducts()
747{
748        axiom = "";
749        std::list<fL_Word *>::iterator i;
750        for (i = genotype.begin(); i != genotype.end(); i++)
751        {
752                axiom += (*i)->stringify(false);
753        }
754        return axiom;
755}
756
757SString fL_Builder::toString()
758{
759        SString res;
760        for (std::unordered_map<std::string, fL_Word *>::iterator it = words.begin(); it != words.end(); it++)
761        {
762                if (!it->second->builtin)
763                {
764                        res += it->second->toString();
765                }
766        }
767        getStringifiedProducts();
768        removeRedundantRules();
769        Param par(fL_builder_paramtab);
770        fL_Builder *obj = new fL_Builder();
771        par.select(this);
772        SString tmp;
773        par.saveSingleLine(tmp, obj, true, false);
774        res += SString("i:") + tmp;
775        delete obj;
776        for (fL_Rule * rule : rules)
777        {
778                res += rule->toString();
779        }
780        return res;
781}
782
783int fL_Rule::deploy(fL_Builder *builder, fL_Word *in, std::list<fL_Word *>::iterator &it, double currtime)
784{
785        // if predecessor and given word differ, then rule is not applicable
786        if (in->name != objpred->name || in->npar != objpred->npar)
787        {
788                return 1;
789        }
790        // store predecessor values in separate array
791        double *inwordvalues = new double[in->npar];
792        for (int i = 0; i < in->npar; i++)
793        {
794                if (in->parevals[i] != NULL)
795                {
796                        in->parevals[i]->modifyVariable(-1, currtime == in->creationiter + 1.0 ? 1.0 : currtime - floor(currtime));
797                        in->parevals[i]->evaluateRPN(inwordvalues[i]);
798                }
799                else
800                {
801                        inwordvalues[i] = 0;
802                }
803        }
804        // if condition exists
805        if (condeval)
806        {
807                // check if condition is satisfied. If not, rule is not applicable
808                for (int i = 0; i < in->npar; i++)
809                {
810                        condeval->modifyVariable(i, inwordvalues[i]);
811                }
812                double condvalue;
813                condeval->evaluateRPN(condvalue);
814                if (condvalue == 0)
815                {
816                        delete[] inwordvalues;
817                        return 1;
818                }
819        }
820
821        // remove predecessor word from genotype and replace it with successor
822        it = builder->genotype.erase(it);
823        for (std::list<fL_Word *>::iterator word = objsucc.begin(); word != objsucc.end(); word++)
824        {
825                // create new word and copy properties from word definition
826                fL_Word *nword = new fL_Word(false, begin, end);
827                *nword = **word;
828                // store information about when word has been created
829                nword->creationiter = currtime;
830                nword->parevals.clear();
831                if (nword->npar > 0)
832                {
833                        nword->data = ParamObject::makeObject(nword->tab);
834                }
835                // calculate word parameters and store MathEvaluation objects for further
836                // time manipulations.
837                Param par((*word)->tab, (*word)->data);
838                Param npar(nword->tab, nword->data);
839                for (int q = 0; q < nword->npar; q++)
840                {
841                        if ((*word)->parevals[q] == NULL)
842                        {
843                                if ((*word)->builtin && (strcmp(npar.id(q), "d") == 0))
844                                {
845                                        SString t = par.getString(q);
846                                        npar.setString(q, t);
847                                        nword->parevals.push_back(NULL);
848                                }
849                                if ((*word)->builtin && (strcmp(npar.id(q), FL_PE_CONN_ATTR) == 0))
850                                {
851                                        SString t = par.getString(q);
852                                        if (t.length() > 0)
853                                        {
854                                                fL_Word *attrword = NULL;
855                                                builder->createWord(t, attrword, in->npar, begin, end);
856                                                for (int j = 0; j < attrword->npar; j++)
857                                                {
858                                                        if (attrword->parevals[j])
859                                                        {
860                                                                for (int i = 0; i < in->npar; i++)
861                                                                {
862                                                                        attrword->parevals[j]->modifyVariable(i, inwordvalues[i]);
863                                                                }
864                                                        }
865                                                }
866                                                SString res = attrword->stringify(false);
867                                                npar.setString(q, res);
868                                                nword->parevals.push_back(NULL);
869                                                delete attrword;
870                                        }
871                                }
872                                else
873                                {
874                                        //MathEvaluation *ev = new MathEvaluation(0);
875                                        //ev->convertString("0");
876                                        //nword->parevals.push_back(ev);
877                                        nword->parevals.push_back(NULL);
878                                }
879                        }
880                        else
881                        {
882                                std::string tmp;
883                                (*word)->parevals[q]->RPNToInfix(tmp);
884                                MathEvaluation *ev = new MathEvaluation(in->npar);
885                                for (int i = 0; i < in->npar; i++)
886                                {
887                                        ev->modifyVariable(i, inwordvalues[i]);
888                                }
889                                ev->modifyVariable(-1, currtime == (*word)->creationiter + 1.0 ? 1.0 : currtime - floor(currtime));
890                                ev->convertString(tmp);
891                                nword->parevals.push_back(ev);
892                        }
893                }
894                builder->genotype.insert(it, nword);
895        }
896        delete[] inwordvalues;
897        delete in;
898        return 0;
899}
900
901int fL_Builder::iterate(double currtime)
902{
903        // deploy proper rules for all words in current genotype
904        std::list<fL_Word *>::iterator word = genotype.begin();
905        while (word != genotype.end())
906        {
907                bool deployed = false;
908                for (fL_Rule * rule : rules)
909                {
910                        if (rule->deploy(this, (*word), word, currtime) == 0)
911                        {
912                                deployed = true;
913                                break;
914                        }
915                }
916                if (!deployed) word++;
917        }
918        return 0;
919}
920
921int fL_Builder::alterTimedProperties(double currtime)
922{
923        // alter parameters of all words, if they are time-dependent
924        std::list<fL_Word *>::iterator word = genotype.begin();
925        while (word != genotype.end())
926        {
927                if (currtime - (*word)->creationiter <= 1.0)
928                {
929                        for (MathEvaluation *ev : (*word)->parevals)
930                        {
931                                if (ev) ev->modifyVariable(-1, currtime == (*word)->creationiter + 1.0 ? 1.0 : currtime - floor(currtime));
932                        }
933                }
934                word++;
935        }
936        return 0;
937}
938
939int fL_Builder::alterPartProperties(Part *part, fL_Word *stickword, double &alterationcount)
940{
941        Param par(stickword->tab, stickword->data);
942        Param ppar = part->properties();
943        for (int i = 0; i < FL_PART_PROPS_COUNT; i++)
944        {
945                double mn, mx, df;
946                ppar.getMinMaxDouble(ppar.findId(fL_part_names[i]), mn, mx, df);
947                double currval;
948                if (!stickword->parevals[i])
949                {
950                        currval = df;
951                }
952                else
953                {
954                        stickword->parevals[i]->evaluateRPN(currval);
955                        currval = sigmoidTransform(currval, mn, mx);
956                }
957                double partprop = (ppar.getDoubleById(fL_part_names[i]) * alterationcount +
958                        currval) / (alterationcount + 1.0);
959                ppar.setDoubleById(fL_part_names[i], partprop);
960        }
961        return 0;
962}
963
964double fL_Word::distance(fL_Word *right)
965{
966        if (name != right->name || npar != right->npar)
967        {
968                return -1;
969        }
970        double distance = 0;
971        for (int i = 0; i < npar; i++)
972        {
973                double l = 0;
974                double r = 0;
975                if (parevals[i]) parevals[i]->evaluateRPN(l);
976                if (right->parevals[i]) right->parevals[i]->evaluateRPN(r);
977                distance += (l - r) * (l - r);
978        }
979        return sqrt(distance);
980}
981
982Neuro *fL_Builder::findInputNeuron(std::pair<std::list<fL_Word *>::iterator, Neuro *> currneu, fL_Word *attractor)
983{
984        if (!attractor)
985        {
986                std::list<fL_Word *>::reverse_iterator riter(currneu.first);
987                std::list<fL_Word *>::iterator iter(currneu.first);
988                iter++;
989                while (riter != genotype.rend() || iter != genotype.end())
990                {
991                        if (iter != genotype.end())
992                        {
993                                if ((*iter)->name == "N" && (*iter)->bodyelementpointer != currneu.second)
994                                {
995                                        return (Neuro *)(*iter)->bodyelementpointer;
996                                }
997                                iter++;
998                        }
999                        if (riter != genotype.rend())
1000                        {
1001                                if ((*riter)->name == "N" && (*riter)->bodyelementpointer != currneu.second)
1002                                {
1003                                        return (Neuro *)(*riter)->bodyelementpointer;
1004                                }
1005                                riter++;
1006                        }
1007                }
1008                return NULL;
1009        }
1010        else
1011        {
1012                double mindistance = -1;
1013                std::list<fL_Word *>::iterator minit = genotype.end();
1014                for (std::list<fL_Word *>::iterator it = genotype.begin(); it != genotype.end(); it++)
1015                {
1016                        double currdist = attractor->distance((*it));
1017                        if (currdist != -1 && (currdist < mindistance || mindistance == -1))
1018                        {
1019                                mindistance = currdist;
1020                                minit = it;
1021                        }
1022                }
1023                if (minit != genotype.end())
1024                {
1025                        for (; minit != genotype.end(); minit++)
1026                        {
1027                                if ((*minit)->name == "N" && (*minit)->bodyelementpointer)
1028                                {
1029                                        Neuro *n = (Neuro *)(*minit)->bodyelementpointer;
1030                                        if (n->getClass()->getPreferredOutput() != 0)
1031                                        {
1032                                                return n;
1033                                        }
1034                                }
1035                        }
1036                }
1037        }
1038        return NULL;
1039}
1040
1041double fL_Builder::sigmoidTransform(double input, double mn, double mx)
1042{
1043        return mn + (mx - mn) * (1.0 / (1.0 + exp(-input)));
1044}
1045
1046int fL_Builder::buildModelFromSequence(Model *model)
1047{
1048        fL_State currstate;
1049        std::unordered_map<Part *, double> counters;
1050        std::stack<fL_State> statestack;
1051        std::vector<std::pair<std::list<fL_Word *>::iterator, Neuro *>> connsbuffer;
1052        Part *firstpart = NULL;
1053
1054        for (std::list<fL_Word *>::iterator w = genotype.begin(); w != genotype.end(); w++)
1055        {
1056                fL_Word *word = (*w);
1057                if (word->builtin)
1058                {
1059                        if (word->name == "S")
1060                        {
1061                                if (!currstate.currpart)
1062                                {
1063                                        if (!firstpart)
1064                                        {
1065                                                firstpart = new Part();
1066                                                firstpart->p = Pt3D_0;
1067                                                counters[firstpart] = 0;
1068                                                model->addPart(firstpart);
1069                                                if (using_mapping) firstpart->addMapping(IRange(word->begin, word->end));
1070                                        }
1071                                        currstate.currpart = firstpart;
1072                                }
1073                                if (alterPartProperties(currstate.currpart, word, counters[currstate.currpart]) != 0)
1074                                {
1075                                        return 1;
1076                                }
1077                                counters[currstate.currpart] += 1;
1078                                Part *newpart = new Part();
1079                                counters[newpart] = 0;
1080                                if (alterPartProperties(newpart, word, counters[newpart]) != 0)
1081                                {
1082                                        delete newpart;
1083                                        return 1;
1084                                }
1085                                Param par(word->tab, word->data);
1086                                double length;
1087                                if (!word->parevals[FL_PART_PROPS_COUNT + FL_JOINT_PROPS_COUNT])
1088                                {
1089                                        length = FL_DEFAULT_LENGTH; // default length value
1090                                }
1091                                else
1092                                {
1093                                        double parsedval = 0.0;
1094                                        if (word->parevals[FL_PART_PROPS_COUNT + FL_JOINT_PROPS_COUNT]->evaluateRPN(parsedval) != 0)
1095                                        {
1096                                                delete newpart;
1097                                                logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing word parameter");
1098                                                return 1;
1099                                        }
1100                                        length = sigmoidTransform(parsedval, FL_MINIMAL_LENGTH, FL_MAXIMAL_LENGTH);
1101                                }
1102                                newpart->p = currstate.currpart->p + currstate.direction * length;
1103                                counters[newpart] += 1;
1104                                model->addPart(newpart);
1105                                if (using_mapping) newpart->addMapping(IRange(word->begin, word->end));
1106                                Joint *newjoint = new Joint();
1107                                newjoint->attachToParts(currstate.currpart, newpart);
1108
1109                                Param jpar = newjoint->properties();
1110                                for (int i = 0; i < FL_JOINT_PROPS_COUNT; i++)
1111                                {
1112                                        double mn, mx, df;
1113                                        jpar.getMinMaxDouble(jpar.findId(fL_joint_names[i]), mn, mx, df);
1114                                        double jointprop;
1115                                        if (!word->parevals[FL_PART_PROPS_COUNT + i])
1116                                        {
1117                                                jointprop = df; // assign default value
1118                                        }
1119                                        else
1120                                        {
1121                                                if (word->parevals[FL_PART_PROPS_COUNT + i]->evaluateRPN(jointprop) != 0)
1122                                                {
1123                                                        logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing word parameter");
1124                                                        delete newjoint;
1125                                                        return 1;
1126                                                }
1127                                                jointprop = sigmoidTransform(jointprop, mn, mx);
1128                                        }
1129                                        jpar.setDoubleById(fL_joint_names[i], jointprop);
1130                                }
1131                                model->addJoint(newjoint);
1132                                if (using_mapping) newjoint->addMapping(IRange(word->begin, word->end));
1133                                currstate.currpart = newpart;
1134                        }
1135                        else if (word->name == "N")
1136                        {
1137                                Param npar(word->tab, word->data);
1138                                Neuro *neu = new Neuro();
1139                                SString details = npar.getStringById("d");
1140                                if (details == "")
1141                                {
1142                                        details = "N";
1143                                }
1144                                neu->setDetails(details);
1145                                if (!neu->getClass())
1146                                {
1147                                        logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing neuron class");
1148                                        delete neu;
1149                                        return 1;
1150                                }
1151                                model->addNeuro(neu);
1152
1153
1154                                // MacKo 2023-07: embody (i.e., attach to body) sensors and effectors.
1155                                // Ad hoc modification; should be reconsidered as it was added without reminding and understanding of the entire logic of the phenotype development and assumptions, and may not be "the best performing" or "the most reasonable" approach. Without this embodiment, genotypes with sensors and effectors not attached to body had warnings (such neurons "could not be initialized") so such genotypes were not accepted when creatwarnfail==1; even with creatwarnfail==0, non-embodied sensor and effector neurons were useless.
1156                                //printf("Neuron: -------------- %s\n", details.c_str());
1157                                switch (neu->getClass()->getPreferredLocation())
1158                                {
1159                                case NeuroClass::PrefLocation::PREFER_PART: //attach to currstate.currpart
1160                                        if (currstate.currpart != NULL)
1161                                                neu->attachToPart(currstate.currpart);
1162                                        break;
1163                                case NeuroClass::PrefLocation::PREFER_JOINT: //attach to the last joint incident with currstate.currpart
1164                                        if (currstate.currpart != NULL)
1165                                        {
1166                                                Joint *j = NULL;
1167                                                for (int i = 0; i < model->getJointCount(); i++)
1168                                                {
1169                                                        Joint *jj = model->getJoint(i);
1170                                                        if (jj->part1 == currstate.currpart || jj->part2 == currstate.currpart)
1171                                                                j = jj;
1172                                                }
1173                                                if (j != NULL)
1174                                                        neu->attachToJoint(j);
1175                                        }
1176                                        break;
1177                                default:
1178                                        break;
1179                                }
1180
1181
1182                                if (using_mapping) neu->addMapping(IRange(word->begin, word->end));
1183                                if (neu->getClass()->getPreferredInputs() != 0)
1184                                {
1185                                        currstate.currneuron = neu;
1186                                }
1187                                word->bodyelementpointer = neu;
1188                        }
1189                        else if (word->name == "C")
1190                        {
1191                                connsbuffer.push_back({ w, currstate.currneuron });
1192                        }
1193                        else if (word->name.startsWith("rot"))
1194                        {
1195                                Orient rotmatrix = Orient_1;
1196                                double rot;
1197                                if (!word->parevals[0])
1198                                {
1199                                        rot = 0;
1200                                }
1201                                else if (word->parevals[0]->evaluateRPN(rot) != 0)
1202                                {
1203                                        logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing rotation word");
1204                                        return 1;
1205                                }
1206
1207                                rot = sigmoidTransform(rot, -M_PI, M_PI);
1208
1209                                if (word->name == "rotX")
1210                                {
1211                                        rotmatrix.rotate(Pt3D(rot, 0, 0));
1212                                }
1213                                else if (word->name == "rotY")
1214                                {
1215                                        rotmatrix.rotate(Pt3D(0, rot, 0));
1216                                }
1217                                else if (word->name == "rotZ")
1218                                {
1219                                        rotmatrix.rotate(Pt3D(0, 0, rot));
1220                                }
1221                                currstate.direction = rotmatrix.transform(currstate.direction);
1222                                currstate.direction.normalize();
1223                        }
1224                        else if (word->name == "[")
1225                        {
1226                                statestack.push(currstate);
1227                        }
1228                        else if (word->name == "]")
1229                        {
1230                                currstate = statestack.top();
1231                                statestack.pop();
1232                        }
1233                }
1234        }
1235
1236        // connections need
1237        // std::pair<std::list<fL_Word *>::iterator, Neuro *> conndata : connsbuffer
1238        for (unsigned int i = 0; i < connsbuffer.size(); i++)
1239        {
1240                if (connsbuffer[i].second == NULL ||
1241                        (connsbuffer[i].second->getClass()->getPreferredInputs() != -1 &&
1242                                connsbuffer[i].second->getInputCount() >=
1243                                connsbuffer[i].second->getClass()->getPreferredInputs()))
1244                {
1245                        // since connections are separated entities from neurons, it may happen
1246                        // that there will be no neuron to connect to
1247                        // logMessage("fL_Builder", "developModel", LOG_DEBUG, "Connection could not be established");
1248                }
1249                else
1250                {
1251                        Param par((*connsbuffer[i].first)->tab, (*connsbuffer[i].first)->data);
1252                        SString attr = par.getStringById(FL_PE_CONN_ATTR);
1253                        fL_Word *attractor = NULL;
1254                        if (attr.length() > 0)
1255                        {
1256                                createWord(attr, attractor, 0, (*connsbuffer[i].first)->begin, (*connsbuffer[i].first)->end);
1257                        }
1258                        Neuro *neu = findInputNeuron(connsbuffer[i], attractor);
1259                        double weight = 0.0;
1260                        if ((*connsbuffer[i].first)->parevals[0])
1261                        {
1262                                if ((*connsbuffer[i].first)->parevals[0]->evaluateRPN(weight) != 0)
1263                                {
1264                                        logMessage("fL_Builder", "developModel", LOG_ERROR,
1265                                                "Error parsing word parameter");
1266                                        delete attractor;
1267                                        return 1;
1268                                }
1269                        }
1270                        if (neu)
1271                        {
1272                                connsbuffer[i].second->addInput(neu, weight);
1273                                if (using_mapping) neu->addMapping(
1274                                        IRange((*connsbuffer[i].first)->begin,
1275                                                (*connsbuffer[i].first)->end));
1276                        }
1277                        else
1278                        {
1279                                connsbuffer[i].second->addInput(connsbuffer[i].second, weight);
1280                                if (using_mapping) connsbuffer[i].second->addMapping( //MacKo 2023-07: changed 'neu' (which is NULL here so crashed) to connsbuffer[i].second (no guarantee this is a proper fix, only inspired by the analogy to the difference in both branches of "if" in the line above)
1281                                        IRange((*connsbuffer[i].first)->begin,
1282                                                (*connsbuffer[i].first)->end));
1283                        }
1284                        delete attractor;
1285                }
1286        }
1287        return 0;
1288}
1289
1290void fL_Builder::clearModelElements(Model *m)
1291{
1292        for (int i = 0; i < m->getJointCount(); i++)
1293        {
1294                m->removeJoint(i, 0);
1295        }
1296        for (int i = 0; i < m->getNeuroCount(); i++)
1297        {
1298                m->removeNeuro(i, true);
1299        }
1300        for (int i = 0; i < m->getNeuroCount(); i++)
1301        {
1302                m->removePart(i, 0, 0);
1303        }
1304        m->clearMap();
1305}
1306
1307Model* fL_Builder::developModel(double &neededtime)
1308{
1309        double curriter = 0;
1310        double timestamp = 1.0 / numckp;
1311        double t = 0;
1312        Model *m = new Model();
1313        m->open(using_checkpoints);
1314        bool wordsexceeded = false;
1315        for (; t <= time; t += timestamp)
1316        {
1317                alterTimedProperties(t); // always alter timed properties in the beginning
1318                // if iteration exceeds integer value, then deploy rules
1319                if (floor(t) > curriter)
1320                {
1321                        iterate(t);
1322                        curriter += 1.0;
1323                }
1324                if (using_checkpoints)
1325                {
1326                        clearModelElements(m);
1327                        if (buildModelFromSequence(m) != 0)
1328                        {
1329                                delete m;
1330                                return NULL;
1331                        }
1332                        m->checkpoint();
1333                }
1334                if (maxwords != -1 && ((int)genotype.size()) > maxwords)
1335                {
1336                        wordsexceeded = true;
1337                        break;
1338                }
1339        }
1340
1341        if (wordsexceeded)
1342        {
1343                neededtime = t;
1344        }
1345        else
1346        {
1347                neededtime = time;
1348        }
1349
1350        // if exact time of development was not reached due to floating point errors,
1351        // then alter timed properties
1352        if (time < t)
1353        {
1354                alterTimedProperties(time);
1355        }
1356        clearModelElements(m);
1357        if (buildModelFromSequence(m) != 0)
1358        {
1359                delete m;
1360                return NULL;
1361        }
1362        if (using_checkpoints)
1363        {
1364                m->checkpoint();
1365        }
1366        m->close();
1367        return m;
1368}
1369
1370int fL_Builder::countSticksInSequence(std::list<fL_Word *> *sequence)
1371{
1372        int count = 0;
1373        for (std::list<fL_Word *>::iterator it = sequence->begin(); it != sequence->end(); it++)
1374        {
1375                if ((*it)->builtin && (*it)->name == "S")
1376                {
1377                        count++;
1378                }
1379        }
1380        return count;
1381}
1382
1383int fL_Builder::countDefinedWords()
1384{
1385        return words.size() - builtincount;
1386}
1387
1388int fL_Builder::countWordsInLSystem()
1389{
1390        int count = genotype.size();
1391        for (fL_Rule *rul : rules)
1392        {
1393                count += rul->objsucc.size();
1394        }
1395        count += words.size();
1396        return count;
1397}
1398
1399void fL_Builder::removeRedundantRules()
1400{
1401        for (std::vector<fL_Rule *>::iterator it = rules.begin();
1402                it != rules.end(); it++)
1403        {
1404                std::vector<fL_Rule *>::iterator it2 = it;
1405                it2++;
1406                while (it2 != rules.end())
1407                {
1408                        bool todelete = false;
1409                        if ((*it)->objpred->name == (*it2)->objpred->name)
1410                        {
1411                                if ((*it)->condeval == NULL && (*it2)->condeval == NULL)
1412                                {
1413                                        todelete = true;
1414                                }
1415                                else if ((*it)->condeval == NULL && (*it2)->condeval != NULL)
1416                                {
1417                                        std::iter_swap(it, it2);
1418                                }
1419                                else if ((*it)->condeval != NULL && (*it2)->condeval != NULL)
1420                                {
1421                                        if ((*it)->condeval->getStringifiedRPN() ==
1422                                                (*it2)->condeval->getStringifiedRPN())
1423                                        {
1424                                                todelete = true;
1425                                        }
1426                                }
1427                        }
1428                        if (todelete)
1429                        {
1430                                delete (*it2);
1431                                it2 = rules.erase(it2);
1432                        }
1433                        else
1434                        {
1435                                it2++;
1436                        }
1437                }
1438        }
1439}
Note: See TracBrowser for help on using the repository browser.