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