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