source: cpp/frams/genetics/genooperators.cpp

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

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

  • Property svn:eol-style set to native
File size: 22.1 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2024  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include <ctype.h>  //isupper()
6#include <algorithm> // std::min, std::max
7#include <cmath> // std::floor()
8#include "genooperators.h"
9#include <common/log.h>
10#include <common/nonstd_math.h>
11#include <frams/util/rndutil.h>
12
13//
14// custom distributions for mutations of various parameters
15//
16static double distrib_force[] =   // for '!'
17{
18        3,             // distribution 0 -__/ +1
19        0.001, 0.2,    // "slow" neurons
20        0.001, 1,
21        1, 1,          // "fast" neurons
22};
23static double distrib_inertia[] =  // for '='
24{
25        2,             // distribution 0 |..- +1
26        0, 0,          // "fast" neurons
27        0.7, 0.98,
28};
29static double distrib_sigmo[] =  // for '/'
30{
31        5,             // distribution -999 -..-^-..- +999
32        -999, -999,    //"perceptron"
33        999, 999,
34        -5, -1,        // nonlinear
35        1, 5,
36        -1, 1,         // ~linear
37};
38/*
39static double distrib_weight[] =
40{
415,                 // distribution -999 _-^_^-_ +999
42-999, 999,         // each weight value may be useful, especially...
43-5, -0.3,          // ...little non-zero values
44-3, -0.6,
450.6, 3,
460.3, 5,
47};
48*/
49
50int GenoOperators::roulette(const double *probtab, const int count)
51{
52        double sum = 0;
53        int i;
54        for (i = 0; i < count; i++) sum += probtab[i];
55        double sel = rndDouble(sum);
56        for (sum = 0, i = 0; i < count; i++) { sum += probtab[i]; if (sel < sum) return i; }
57        return -1;
58}
59
60int GenoOperators::roulette(const vector<double> &probtab)
61{
62        return roulette(probtab.data(), (int)probtab.size());
63}
64
65bool GenoOperators::getMinMaxDef(ParamInterface *p, int i, double &mn, double &mx, double &def)
66{
67        mn = mx = def = 0;
68        int defined = 0;
69        if (p->type(i)[0] == 'f')
70        {
71                double _mn = 0, _mx = 1, _def = 0.5;
72                defined = p->getMinMaxDouble(i, _mn, _mx, _def);
73                if (defined == 1) _mx = _mn + 1000.0; //only min was defined, so let's set some arbitrary range, just to have some freedom. Assumes _mn is not close to maxdouble...
74                if (_mx < _mn && defined == 3) //only default was defined, so let's assume some arbitrary range. Again, no check for min/maxdouble...
75                {
76                        _mn = _def - 500.0;
77                        _mx = _def + 500.0;
78                }
79                if (defined < 3) _def = (_mn + _mx) / 2.0;
80                mn = _mn; mx = _mx; def = _def;
81        }
82        if (p->type(i)[0] == 'd')
83        {
84                paInt _mn = 0, _mx = 1, _def = 0;
85                defined = p->getMinMaxInt(i, _mn, _mx, _def);
86                if (defined == 1) _mx = _mn + 1000; //only min was defined, so let's set some arbitrary range, just to have some freedom. Assumes _mn is not close to maxint...
87                if (_mx < _mn && defined == 3) //only default was defined, so let's assume some arbitrary range. Again, no check for min/maxint...
88                {
89                        _mn = _def - 500;
90                        _mx = _def + 500;
91                }
92                if (defined < 3) _def = (_mn + _mx) / 2;
93                mn = _mn; mx = _mx; def = _def;
94        }
95        return defined == 3;
96}
97
98bool GenoOperators::mutateRandomNeuroClassProperty(Neuro* n)
99{
100        bool mutated = false;
101        int prop = selectRandomNeuroClassProperty(n);
102        if (prop >= 0)
103        {
104                if (prop >= GenoOperators::NEUROCLASS_PROP_OFFSET)
105                {
106                        SyntParam par = n->classProperties();   //commits changes when this object is destroyed
107                        mutated = mutateProperty(par, prop - GenoOperators::NEUROCLASS_PROP_OFFSET);
108                }
109                else
110                {
111                        Param par = n->extraProperties();
112                        mutated = mutateProperty(par, prop);
113                }
114        }
115        return mutated;
116}
117
118int GenoOperators::selectRandomNeuroClassProperty(Neuro *n)
119{
120        int neuext = n->extraProperties().getPropCount(),
121                neucls = n->getClass() == NULL ? 0 : n->getClass()->getProperties().getPropCount();
122        if (neuext + neucls == 0) return -1; //no properties in this neuron
123        int index = rndUint(neuext + neucls);
124        if (index >= neuext) index = index - neuext + NEUROCLASS_PROP_OFFSET;
125        return index;
126}
127
128double GenoOperators::getMutatedNeuroClassProperty(double current, Neuro *n, int i)
129{
130        if (i == -1)
131        {
132                logPrintf("GenoOperators", "getMutatedNeuroClassProperty", LOG_WARN, "Deprecated usage in C++ source: to mutate connection weight, use getMutatedNeuronConnectionWeight().");
133                return getMutatedNeuronConnectionWeight(current);
134        }
135        Param p;
136        if (i >= NEUROCLASS_PROP_OFFSET) { i -= NEUROCLASS_PROP_OFFSET; p = n->getClass()->getProperties(); }
137        else p = n->extraProperties();
138        double newval = current;
139        /*bool ok=*/getMutatedProperty(p, i, current, newval);
140        return newval;
141}
142
143double GenoOperators::getMutatedNeuronConnectionWeight(double current)
144{
145        return mutateCreepNoLimit('f', current, 2, true);
146}
147
148bool GenoOperators::mutatePropertyNaive(ParamInterface &p, int i)
149{
150        double mn, mx, df;
151        if (p.type(i)[0] != 'f' && p.type(i)[0] != 'd') return false; //don't know how to mutate
152        getMinMaxDef(&p, i, mn, mx, df);
153
154        ExtValue ev;
155        p.get(i, ev);
156        ev.setDouble(mutateCreep(p.type(i)[0], ev.getDouble(), mn, mx, true));
157        p.set(i, ev);
158        return true;
159}
160
161bool GenoOperators::mutateProperty(ParamInterface &p, int i)
162{
163        double newval;
164        ExtValue ev;
165        p.get(i, ev);
166        bool ok = getMutatedProperty(p, i, ev.getDouble(), newval);
167        if (ok) { ev.setDouble(newval); p.set(i, ev); }
168        return ok;
169}
170
171bool GenoOperators::getMutatedProperty(ParamInterface &p, int i, double oldval, double &newval)
172{
173        newval = 0;
174        if (p.type(i)[0] != 'f' && p.type(i)[0] != 'd') return false; //don't know how to mutate
175        const char *n = p.id(i), *na = p.name(i);
176        if (strcmp(n, "si") == 0 && strcmp(na, "Sigmoid") == 0) newval = round(CustomRnd(distrib_sigmo), 3); else
177                if (strcmp(n, "in") == 0 && strcmp(na, "Inertia") == 0) newval = round(CustomRnd(distrib_inertia), 3); else
178                        if (strcmp(n, "fo") == 0 && strcmp(na, "Force") == 0) newval = round(CustomRnd(distrib_force), 3); else
179                        {
180                                double mn, mx, df;
181                                getMinMaxDef(&p, i, mn, mx, df);
182                                newval = mutateCreep(p.type(i)[0], oldval, mn, mx, true);
183                        }
184        return true;
185}
186
187double GenoOperators::mutateCreepNoLimit(char type, double current, double stddev, bool limit_precision_3digits)
188{
189        double result = RndGen.Gauss(current, stddev);
190        if (type == 'd')
191        {
192                result = int(result + 0.5);
193                if (result == current) result += rndUint(2) * 2 - 1; //force some change
194        }
195        else
196        {
197                if (limit_precision_3digits)
198                        result = round(result, 3);
199        }
200        return result;
201}
202
203double GenoOperators::mutateCreep(char type, double current, double mn, double mx, double stddev, bool limit_precision_3digits)
204{
205        double result = mutateCreepNoLimit(type, current, stddev, limit_precision_3digits);
206        if (result<mn || result>mx) //exceeds boundary, so bring to the allowed range
207        {
208                //reflect:
209                if (result > mx) result = mx - (result - mx); else
210                        if (result < mn) result = mn + (mn - result);
211                //wrap (just in case 'result' exceeded the allowed range so much that after the reflection above it exceeded the other boundary):
212                if (result > mx) result = mn + fmod(result - mx, mx - mn); else
213                        if (result < mn) result = mn + fmod(mn - result, mx - mn);
214                if (limit_precision_3digits)
215                {
216                        //reflect and wrap above may have changed the (limited) precision, so try to round again (maybe unnecessarily, because we don't know if reflect+wrap above were triggered)
217                        double result_try = round(result, 3);
218                        if (mn <= result_try && result_try <= mx) result = result_try; //after rounding still within allowed range, so keep rounded value
219                }
220        }
221        clipNegativeZeroIfNeeded(result, mn); //so we don't get -0.0 when minimum is 0.0
222        return result;
223}
224
225double GenoOperators::mutateCreep(char type, double current, double mn, double mx, bool limit_precision_3digits)
226{
227        double stddev = (mx - mn) / 2 / 5; // magic arbitrary formula for stddev, which becomes /halfinterval, 5 times narrower
228        return mutateCreep(type, current, mn, mx, stddev, limit_precision_3digits);
229}
230
231void GenoOperators::setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value)
232{
233        // Deterministic rounding to the closest integer:
234        //value += 0.5; // value==2.499 will become int 2 and value==2.5 will become int 3, but we want these cases to be 2 or 3 with almost equal probability (stochastic rounding).
235
236        //stochastic rounding (value==2.1 should turn in most cases to int 2, rarely to int 3; value==-2.1 should become mostly int -2, rarely int -3):
237        double lower = std::floor(value);
238        value = rndDouble(1) < (value - lower) ? lower + 1 : lower;
239
240        p.setInt(index, (paInt)value);
241}
242
243void GenoOperators::linearMix(vector<double> &p1, vector<double> &p2, double proportion)
244{
245        if (p1.size() != p2.size())
246        {
247                logPrintf("GenoOperators", "linearMix", LOG_ERROR, "Cannot mix vectors of different length (%d and %d)", p1.size(), p2.size());
248                return;
249        }
250        for (unsigned int i = 0; i < p1.size(); i++)
251        {
252                double v1 = p1[i];
253                double v2 = p2[i];
254                p1[i] = v1 * proportion + v2 * (1 - proportion);
255                p2[i] = v2 * proportion + v1 * (1 - proportion);
256        }
257}
258
259void GenoOperators::linearMix(ParamInterface &p1, int i1, ParamInterface &p2, int i2, double proportion)
260{
261        char type1 = p1.type(i1)[0];
262        char type2 = p2.type(i2)[0];
263        if (type1 == 'f' && type2 == 'f')
264        {
265                double v1 = p1.getDouble(i1);
266                double v2 = p2.getDouble(i2);
267                p1.setDouble(i1, v1 * proportion + v2 * (1 - proportion));
268                p2.setDouble(i2, v2 * proportion + v1 * (1 - proportion));
269        }
270        else
271                if (type1 == 'd' && type2 == 'd')
272                {
273                        int v1 = p1.getInt(i1);
274                        int v2 = p2.getInt(i2);
275                        setIntFromDoubleWithProbabilisticDithering(p1, i1, v1 * proportion + v2 * (1 - proportion));
276                        setIntFromDoubleWithProbabilisticDithering(p2, i2, v2 * proportion + v1 * (1 - proportion));
277                }
278                else
279                        logPrintf("GenoOperators", "linearMix", LOG_WARN, "Cannot mix values of types '%c' and '%c'", type1, type2);
280}
281
282int GenoOperators::getActiveNeuroClassCount(Model::ShapeType for_shape_type)
283{
284        int count = 0;
285        for (int i = 0; i < Neuro::getClassCount(); i++)
286        {
287                NeuroClass *nc = Neuro::getClass(i);
288                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive)
289                        count++;
290        }
291        return count;
292}
293
294NeuroClass *GenoOperators::getRandomNeuroClass(Model::ShapeType for_shape_type)
295{
296        vector<NeuroClass *> active;
297        for (int i = 0; i < Neuro::getClassCount(); i++)
298        {
299                NeuroClass *nc = Neuro::getClass(i);
300                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive)
301                        active.push_back(nc);
302        }
303        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
304}
305
306NeuroClass *GenoOperators::getRandomNeuroClassWithOutput(Model::ShapeType for_shape_type)
307{
308        vector<NeuroClass *> active;
309        for (int i = 0; i < Neuro::getClassCount(); i++)
310        {
311                NeuroClass *nc = Neuro::getClass(i);
312                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive && nc->getPreferredOutput() != 0)
313                        active.push_back(nc);
314        }
315        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
316}
317
318NeuroClass *GenoOperators::getRandomNeuroClassWithInput(Model::ShapeType for_shape_type)
319{
320        vector<NeuroClass *> active;
321        for (int i = 0; i < Neuro::getClassCount(); i++)
322        {
323                NeuroClass *nc = Neuro::getClass(i);
324                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive && nc->getPreferredInputs() != 0)
325                        active.push_back(nc);
326        }
327        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
328}
329
330NeuroClass *GenoOperators::getRandomNeuroClassWithOutputAndWantingNoInputs(Model::ShapeType for_shape_type)
331{
332        vector<NeuroClass *> active;
333        for (int i = 0; i < Neuro::getClassCount(); i++)
334        {
335                NeuroClass *nc = Neuro::getClass(i);
336                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive && nc->getPreferredOutput() != 0 && nc->getPreferredInputs() == 0)
337                        active.push_back(nc);
338        }
339        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
340}
341
342NeuroClass *GenoOperators::getRandomNeuroClassWithOutputAndWantingNoOrAnyInputs(Model::ShapeType for_shape_type)
343{
344        vector<NeuroClass *> active;
345        for (int i = 0; i < Neuro::getClassCount(); i++)
346        {
347                NeuroClass *nc = Neuro::getClass(i);
348                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive && nc->getPreferredOutput() != 0 && nc->getPreferredInputs() <= 0) // getPreferredInputs() should be 0 or -1 (any)
349                        active.push_back(nc);
350        }
351        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
352}
353
354int GenoOperators::getRandomNeuroClassWithOutput(const vector<NeuroClass *> &NClist)
355{
356        vector<int> allowed;
357        for (int i = 0; i < (int)NClist.size(); i++)
358                if (NClist[i]->getPreferredOutput() != 0) //this NeuroClass provides output
359                        allowed.push_back(i);
360        if (allowed.size() == 0) return -1; else return allowed[rndUint(allowed.size())];
361}
362
363int GenoOperators::getRandomNeuroClassWithInput(const vector<NeuroClass *> &NClist)
364{
365        vector<int> allowed;
366        for (int i = 0; i < (int)NClist.size(); i++)
367                if (NClist[i]->getPreferredInputs() != 0) //this NeuroClass wants one input connection or more                 
368                        allowed.push_back(i);
369        if (allowed.size() == 0) return -1; else return allowed[rndUint(allowed.size())];
370}
371
372NeuroClass *GenoOperators::parseNeuroClass(char *&s, ModelEnum::ShapeType for_shape_type)
373{
374        int maxlen = (int)strlen(s);
375        int NClen = 0;
376        NeuroClass *NC = NULL;
377        for (int i = 0; i < Neuro::getClassCount(); i++)
378        {
379                NeuroClass *nci = Neuro::getClass(i);
380                if (!nci->isShapeTypeSupported(for_shape_type))
381                        continue;
382                const char *nciname = nci->name.c_str();
383                int ncinamelen = (int)strlen(nciname);
384                if (maxlen >= ncinamelen && ncinamelen > NClen && (strncmp(s, nciname, ncinamelen) == 0))
385                {
386                        NC = nci;
387                        NClen = ncinamelen;
388                }
389        }
390        s += NClen;
391        return NC;
392}
393
394Neuro *GenoOperators::findNeuro(const Model *m, const NeuroClass *nc)
395{
396        if (!m) return NULL;
397        for (int i = 0; i < m->getNeuroCount(); i++)
398                if (m->getNeuro(i)->getClass() == nc) return m->getNeuro(i);
399        return NULL; //neuron of class 'nc' was not found
400}
401
402int GenoOperators::neuroClassProp(char *&s, NeuroClass *nc, bool also_v1_N_props)
403{
404        int len = (int)strlen(s);
405        int Len = 0, I = -1;
406        if (nc)
407        {
408                Param p = nc->getProperties();
409                for (int i = 0; i < p.getPropCount(); i++)
410                {
411                        const char *n = p.id(i);
412                        int l = (int)strlen(n);
413                        if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = NEUROCLASS_PROP_OFFSET + i; Len = l; }
414                        if (also_v1_N_props) //recognize old symbols of properties:  /=!
415                        {
416                                if (strcmp(n, "si") == 0) n = "/"; else
417                                        if (strcmp(n, "in") == 0) n = "="; else
418                                                if (strcmp(n, "fo") == 0) n = "!";
419                                l = (int)strlen(n);
420                                if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = NEUROCLASS_PROP_OFFSET + i; Len = l; }
421                        }
422                }
423        }
424        Neuro n;
425        Param p = n.extraProperties();
426        for (int i = 0; i < p.getPropCount(); i++)
427        {
428                const char *n = p.id(i);
429                int l = (int)strlen(n);
430                if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = i; Len = l; }
431        }
432        s += Len;
433        return I;
434}
435
436bool GenoOperators::canStartNeuroClassName(const char firstchar)
437{
438        return isupper(firstchar) || firstchar == '|' || firstchar == '@' || firstchar == '*';
439}
440
441bool GenoOperators::isWS(const char c)
442{
443        return c == ' ' || c == '\n' || c == '\t' || c == '\r';
444}
445
446void GenoOperators::skipWS(char *&s)
447{
448        if (s == NULL)
449                logMessage("GenoOperators", "skipWS", LOG_WARN, "NULL reference!");
450        else
451                while (isWS(*s)) s++;
452}
453
454bool GenoOperators::areAlike(char *g1, char *g2)
455{
456        while (*g1 || *g2)
457        {
458                skipWS(g1);
459                skipWS(g2);
460                if (*g1 != *g2) return false; //when difference
461                if (!*g1 && !*g2) break; //both end
462                g1++;
463                g2++;
464        }
465        return true; //equal
466}
467
468char *GenoOperators::strchr_no0(const char *str, char ch)
469{
470        return ch == 0 ? NULL : strchr((char *)str, ch);
471}
472
473double GenoOperators::probOfModifier(const char* mod_def)
474{
475        if (*(mod_def + 1) == '(') //the special syntax with the appended probability value in (...)
476                return std::atof(mod_def + 2); //0.0 when no valid number
477        return 1.0;
478}
479
480char GenoOperators::getRandomModifier(const char *choices)
481{
482        static const char* EXTRA_CHARS = "().0123456789";
483        // this function assumes that EXTRA_CHARS are only used for the special probabilities syntax in "choices", not as valid choice characters.
484        vector<char> allowed; //this could be determined only once for a given "choices", as long as the effect of "choices" is deterministic (i.e., "choices" does not include probabilities)
485        size_t choices_len = strlen(choices);
486        allowed.reserve(choices_len); //max size, avoid reallocations later
487        for (size_t i = 0; i < choices_len; i++)
488        {
489                if (strchr(EXTRA_CHARS, choices[i])) continue; //skip parentheses and numbers
490                double prob = probOfModifier(&choices[i]);
491                if (prob == 1.0 || rndDouble(1) < prob)
492                        allowed.push_back(choices[i]);
493        }
494        if (allowed.size() == 0) return 0; //no char is allowed
495        return allowed[rndUint(allowed.size())];
496}
497
498char GenoOperators::getRandomColorModifier(const char *choices, const char *color_modifiers)
499{
500        vector<char> allowed_colors;
501        vector<double> allowed_probs;
502        size_t colors_len = strlen(color_modifiers);
503        allowed_colors.reserve(colors_len); //max size, avoid reallocations later
504        allowed_probs.reserve(colors_len); //max size, avoid reallocations later
505        for (size_t i = 0; i < colors_len; i++) //for all known color modifiers...
506        {
507                const char *pos = strchr(choices, color_modifiers[i]); //...search in "choices" - i.e., in currently set active modifiers. Note that "choices" may use an extended syntax with numbers and parentheses, such as qM(0.1)Dm(0.1)dG(0.2)C
508                if (pos) //found the color modifier in choices
509                {
510                        allowed_colors.push_back(*pos);
511                        allowed_probs.push_back(probOfModifier(pos));
512                }
513        }
514        // the above "parsing" part could be done only once "choices" changes, not every time we want to get a random color modifier...
515        int idx = roulette(allowed_probs);
516        return idx < 0 ? 0 : allowed_colors[idx];
517}
518
519string GenoOperators::simplifiedModifiers_rR(const string& str)
520{
521        int R = 0; //positive means more 'R', negative means more 'r'
522        for (char c : str)
523        {
524                if (c == 'R') R++; else
525                        if (c == 'r') R--;
526        }
527        R %= 8; // 8 * 45 degrees = 360 degrees. After this, we get R=-7..+7
528
529        /* now, simplify homogeneous sequences of rR longer than 4: for example, rrrrr == RRR and RRRRRR == rr
530        -7      1
531        -6      2
532        -5      3
533        -4      -4 (or 4; we choose +4 meaning we will never see rrrr)
534        -3..3      (no changes)
535        4       4 (or -4)
536        5       -3
537        6       -2
538        7       -1
539        */
540        if (R <= -4) R += 8; //-4 => +4
541        else if (R >= 5) R -= 8;
542
543        return R == 0 ? "" : (R > 0 ? string(R, 'R') : string(-R, 'r'));
544}
545
546//#include <cassert>
547string GenoOperators::simplifiedModifiersFixedOrder(const char *str_of_char_pairs, vector<int> &char_counts)
548{
549        //      assert(strlen(str_of_char_pairs) == char_counts.size());
550        //      assert(char_counts.size() % 2 == 0);
551        const int MAX_NUMBER_SAME_TYPE = 8; // max. number of modifiers of each type (case-sensitive) - mainly for rR, even though for rR, 4 would be sufficient if we assume lower or upper can be chosen as required for minimal length just as simplifiedModifiers_rR() does, e.g. rrrrr==RRR, RRRRRR==rr
552        string simplified;
553        //#define CLUMP_IDENTICAL_MODIFIERS //if GeneProps::normalizeBiol4() is used, this is not good because properties are calculated incrementally, non-linearly, their values are updated after each modifier character and some properties interact with each other due to normalization so they can saturate when clumped, therefore it is better keep the modifiers dispersed to equalize their effects
554#ifdef CLUMP_IDENTICAL_MODIFIERS
555        for (size_t i = 0; i < strlen(str_of_char_pairs); i++)
556                if ((i % 2) == 0) //only even index "i" in str_of_char_pairs
557                        for (int j = 0; j < std::min(MAX_NUMBER_SAME_TYPE, abs(char_counts[i] - char_counts[i + 1])); j++) //assume that an even-index char and the following odd-index char have the opposite influence, so they cancel out.
558                                simplified += str_of_char_pairs[i + (char_counts[i + 1] > char_counts[i])]; //inner loop adds a sequence of same chars such as rrrrr or QQQ
559#else
560        for (size_t i = 0; i < strlen(str_of_char_pairs); i++)
561                if ((i % 2) == 0) //only even index "i" in str_of_char_pairs
562                {
563                        char_counts[i] -= char_counts[i + 1]; //from now on, even items in the vector store the difference between antagonistic modifier symbols; odd items are not needed
564                        char_counts[i] = std::min(std::max(char_counts[i], -MAX_NUMBER_SAME_TYPE), MAX_NUMBER_SAME_TYPE);
565                }
566        int remaining;
567        do {
568                remaining = 0;
569                for (size_t i = 0; i < strlen(str_of_char_pairs); i++)
570                        if ((i % 2) == 0) //only even index "i" in str_of_char_pairs
571                                if (char_counts[i] != 0)
572                                {
573                                        simplified += str_of_char_pairs[i + (char_counts[i] < 0)];
574                                        char_counts[i] += char_counts[i] > 0 ? -1 : +1; //decrease the difference towards zero
575                                        remaining += abs(char_counts[i]);
576                                }
577        } while (remaining > 0);
578#endif
579        return simplified;
580}
581
582string GenoOperators::simplifiedModifiers(const string & original, const char* colorgenes)
583{
584        const int MAX_NUMBER_SAME_TYPE = 5; // max. number of modifiers of each type (case-insensitive). The more characters, the closer we can get to min and max values of a given property at the expense of the length of evolved genotypes. 5 is "close enough", but how close we get to the extreme also depends on the initial value of a given property, which is not always exactly in the middle of min and max. rR is treated separately in simplification because their influence follows different (i.e., simple additive) logic - so the simplifiedModifiersFixedOrder() logic with cancelling out antagonistic modifiers would be appropriate for rR.
585        const int MAX_NUMBER_SAME_TYPE_COLOR = 1; //color does not affect fitness and is used purely for aesthetics, so allow at most 1 char for each r,g,b channel - we get very low resolution of colors (only 3*3*3 combinations), but we spare the genotype length and limit bloat
586        int counter[256] = {}; //initialize with zeros; 256 is unnecessarily too big and redundant, but enables very fast access (indexed directly by the ascii code)
587        string simplified = "";
588        for (int i = int(original.size()) - 1; i >= 0; i--) //iterate from end to begin so it is easier to remove "oldest" = first modifiers
589        {
590                unsigned char c = original[i];
591                if (!std::isalpha(c) || c == 'r' || c == 'R') //ignore non-alphabet characters; also, 'r' and 'R' are handled separately by simplifiedModifiers_rR()
592                        continue;
593                unsigned char lower = std::tolower(c);
594                counter[lower]++;
595                int MAX_NUMBER = strchr(colorgenes, c) != NULL ? MAX_NUMBER_SAME_TYPE_COLOR : MAX_NUMBER_SAME_TYPE;
596                if (counter[lower] <= MAX_NUMBER) //get rid of modifiers that are too numerous - get rid of the first ones in the string (="oldest", the last ones looking from the end), because their influence on the parameter value is the smallest
597                        simplified += c;
598        }
599        std::reverse(simplified.begin(), simplified.end()); //"simplified" was built in reverse order, so need to restore the order that corresponds to "original"
600        return simplifiedModifiers_rR(original) + simplified;
601}
Note: See TracBrowser for help on using the repository browser.