source: cpp/frams/genetics/genooperators.cpp @ 1101

Last change on this file since 1101 was 1032, checked in by Maciej Komosinski, 4 years ago
  • fS: comma as an intuitive separator in genotype instead of weird symbols ;'
  • other minor refactorizations
  • Property svn:eol-style set to native
File size: 14.3 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include <ctype.h>  //isupper()
6#include "genooperators.h"
7#include <common/log.h>
8#include <common/nonstd_math.h>
9#include <frams/util/rndutil.h>
10
11//
12// custom distributions for mutations of various parameters
13//
14static double distrib_force[] =   // for '!'
15{
16        3,             // distribution 0 -__/ +1
17        0.001, 0.2,    // "slow" neurons
18        0.001, 1,
19        1, 1,          // "fast" neurons
20};
21static double distrib_inertia[] =  // for '='
22{
23        2,             // distribution 0 |..- +1
24        0, 0,          // "fast" neurons
25        0.7, 0.98,
26};
27static double distrib_sigmo[] =  // for '/'
28{
29        5,             // distribution -999 -..-^-..- +999
30        -999, -999,    //"perceptron"
31        999, 999,
32        -5, -1,        // nonlinear
33        1, 5,
34        -1, 1,         // ~linear
35};
36/*
37static double distrib_weight[] =
38{
395,                 // distribution -999 _-^_^-_ +999
40-999, 999,         // each weight value may be useful, especially...
41-5, -0.3,          // ...little non-zero values
42-3, -0.6,
430.6, 3,
440.3, 5,
45};
46*/
47
48int GenoOperators::roulette(const double *probtab, const int count)
49{
50        double sum = 0;
51        int i;
52        for (i = 0; i < count; i++) sum += probtab[i];
53        double sel = rndDouble(sum);
54        for (sum = 0, i = 0; i < count; i++) { sum += probtab[i]; if (sel < sum) return i; }
55        return -1;
56}
57
58bool GenoOperators::getMinMaxDef(ParamInterface *p, int i, double &mn, double &mx, double &def)
59{
60        mn = mx = def = 0;
61        int defined = 0;
62        if (p->type(i)[0] == 'f')
63        {
64                double _mn = 0, _mx = 1, _def = 0.5;
65                defined = p->getMinMaxDouble(i, _mn, _mx, _def);
66                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...
67                if (_mx < _mn && defined == 3) //only default was defined, so let's assume some arbitrary range. Again, no check for min/maxdouble...
68                {
69                        _mn = _def - 500.0;
70                        _mx = _def + 500.0;
71                }
72                if (defined < 3) _def = (_mn + _mx) / 2.0;
73                mn = _mn; mx = _mx; def = _def;
74        }
75        if (p->type(i)[0] == 'd')
76        {
77                paInt _mn = 0, _mx = 1, _def = 0;
78                defined = p->getMinMaxInt(i, _mn, _mx, _def);
79                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...
80                if (_mx < _mn && defined == 3) //only default was defined, so let's assume some arbitrary range. Again, no check for min/maxint...
81                {
82                        _mn = _def - 500;
83                        _mx = _def + 500;
84                }
85                if (defined < 3) _def = (_mn + _mx) / 2;
86                mn = _mn; mx = _mx; def = _def;
87        }
88        return defined == 3;
89}
90
91bool GenoOperators::mutateRandomNeuroClassProperty(Neuro* n)
92{
93        bool mutated = false;
94        int prop = selectRandomNeuroClassProperty(n);
95        if (prop >= 0)
96        {
97                if (prop >= GenoOperators::NEUROCLASS_PROP_OFFSET)
98                {
99                        SyntParam par = n->classProperties();   //commits changes when this object is destroyed
100                        mutated = mutateProperty(par, prop - GenoOperators::NEUROCLASS_PROP_OFFSET);
101                }
102                else
103                {
104                        Param par = n->extraProperties();
105                        mutated = mutateProperty(par, prop);
106                }
107        }
108        return mutated;
109}
110
111int GenoOperators::selectRandomNeuroClassProperty(Neuro *n)
112{
113        int neuext = n->extraProperties().getPropCount(),
114                neucls = n->getClass() == NULL ? 0 : n->getClass()->getProperties().getPropCount();
115        if (neuext + neucls == 0) return -1; //no properties in this neuron
116        int index = rndUint(neuext + neucls);
117        if (index >= neuext) index = index - neuext + NEUROCLASS_PROP_OFFSET;
118        return index;
119}
120
121double GenoOperators::getMutatedNeuroClassProperty(double current, Neuro *n, int i)
122{
123        if (i == -1)
124        {
125                logPrintf("GenoOperators", "getMutatedNeuroClassProperty", LOG_WARN, "Deprecated usage in C++ source: to mutate connection weight, use getMutatedNeuronConnectionWeight().");
126                return getMutatedNeuronConnectionWeight(current);
127        }
128        Param p;
129        if (i >= NEUROCLASS_PROP_OFFSET) { i -= NEUROCLASS_PROP_OFFSET; p = n->getClass()->getProperties(); }
130        else p = n->extraProperties();
131        double newval = current;
132        /*bool ok=*/getMutatedProperty(p, i, current, newval);
133        return newval;
134}
135
136double GenoOperators::getMutatedNeuronConnectionWeight(double current)
137{
138        return mutateCreepNoLimit('f', current, 2, true);
139}
140
141bool GenoOperators::mutatePropertyNaive(ParamInterface &p, int i)
142{
143        double mn, mx, df;
144        if (p.type(i)[0] != 'f' && p.type(i)[0] != 'd') return false; //don't know how to mutate
145        getMinMaxDef(&p, i, mn, mx, df);
146
147        ExtValue ev;
148        p.get(i, ev);
149        ev.setDouble(mutateCreep(p.type(i)[0], ev.getDouble(), mn, mx, true));
150        p.set(i, ev);
151        return true;
152}
153
154bool GenoOperators::mutateProperty(ParamInterface &p, int i)
155{
156        double newval;
157        ExtValue ev;
158        p.get(i, ev);
159        bool ok = getMutatedProperty(p, i, ev.getDouble(), newval);
160        if (ok) { ev.setDouble(newval); p.set(i, ev); }
161        return ok;
162}
163
164bool GenoOperators::getMutatedProperty(ParamInterface &p, int i, double oldval, double &newval)
165{
166        newval = 0;
167        if (p.type(i)[0] != 'f' && p.type(i)[0] != 'd') return false; //don't know how to mutate
168        const char *n = p.id(i), *na = p.name(i);
169        if (strcmp(n, "si") == 0 && strcmp(na, "Sigmoid") == 0) newval = round(CustomRnd(distrib_sigmo), 3); else
170                if (strcmp(n, "in") == 0 && strcmp(na, "Inertia") == 0) newval = round(CustomRnd(distrib_inertia), 3); else
171                        if (strcmp(n, "fo") == 0 && strcmp(na, "Force") == 0) newval = round(CustomRnd(distrib_force), 3); else
172                        {
173                                double mn, mx, df;
174                                getMinMaxDef(&p, i, mn, mx, df);
175                                newval = mutateCreep(p.type(i)[0], oldval, mn, mx, true);
176                        }
177        return true;
178}
179
180double GenoOperators::mutateCreepNoLimit(char type, double current, double stddev, bool limit_precision_3digits)
181{
182        double result = RndGen.Gauss(current, stddev);
183        if (type == 'd')
184        {
185                result = int(result + 0.5);
186                if (result == current) result += rndUint(2) * 2 - 1; //force some change
187        }
188        else
189        {
190                if (limit_precision_3digits)
191                        result = round(result, 3);
192        }
193        return result;
194}
195
196double GenoOperators::mutateCreep(char type, double current, double mn, double mx, double stddev, bool limit_precision_3digits)
197{
198        double result = mutateCreepNoLimit(type, current, stddev, limit_precision_3digits);
199        if (result<mn || result>mx) //exceeds boundary, so bring to the allowed range
200        {
201                //reflect:
202                if (result > mx) result = mx - (result - mx); else
203                        if (result < mn) result = mn + (mn - result);
204                //wrap (just in case 'result' exceeded the allowed range so much that after reflection above it exceeded the other boundary):
205                if (result > mx) result = mn + fmod(result - mx, mx - mn); else
206                        if (result < mn) result = mn + fmod(mn - result, mx - mn);
207                if (limit_precision_3digits)
208                {
209                        //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)
210                        double result_try = round(result, 3);
211                        if (mn <= result_try && result_try <= mx) result = result_try; //after rounding still witin allowed range, so keep rounded value
212                }
213        }
214        return result;
215}
216
217double GenoOperators::mutateCreep(char type, double current, double mn, double mx, bool limit_precision_3digits)
218{
219        double stddev = (mx - mn) / 2 / 5; // magic arbitrary formula for stddev, which becomes /halfinterval, 5 times narrower
220        return mutateCreep(type, current, mn, mx, stddev, limit_precision_3digits);
221}
222
223void GenoOperators::setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value) //TODO
224{
225        p.setInt(index, (paInt)(value + 0.5)); //TODO value=2.499 will result in 2 and 2.5 will result in 3, but we want these cases to be 2 or 3 with almost equal probability. value=2.1 should be mostly 2, rarely 3. Careful with negative values (test it!)
226}
227
228void GenoOperators::linearMix(vector<double> &p1, vector<double> &p2, double proportion)
229{
230        if (p1.size() != p2.size())
231        {
232                logPrintf("GenoOperators", "linearMix", LOG_ERROR, "Cannot mix vectors of different length (%d and %d)", p1.size(), p2.size());
233                return;
234        }
235        for (unsigned int i = 0; i < p1.size(); i++)
236        {
237                double v1 = p1[i];
238                double v2 = p2[i];
239                p1[i] = v1 * proportion + v2 * (1 - proportion);
240                p2[i] = v2 * proportion + v1 * (1 - proportion);
241        }
242}
243
244void GenoOperators::linearMix(ParamInterface &p1, int i1, ParamInterface &p2, int i2, double proportion)
245{
246        char type1 = p1.type(i1)[0];
247        char type2 = p2.type(i2)[0];
248        if (type1 == 'f' && type2 == 'f')
249        {
250                double v1 = p1.getDouble(i1);
251                double v2 = p2.getDouble(i2);
252                p1.setDouble(i1, v1 * proportion + v2 * (1 - proportion));
253                p2.setDouble(i2, v2 * proportion + v1 * (1 - proportion));
254        }
255        else
256                if (type1 == 'd' && type2 == 'd')
257                {
258                        int v1 = p1.getInt(i1);
259                        int v2 = p2.getInt(i2);
260                        setIntFromDoubleWithProbabilisticDithering(p1, i1, v1 * proportion + v2 * (1 - proportion));
261                        setIntFromDoubleWithProbabilisticDithering(p2, i2, v2 * proportion + v1 * (1 - proportion));
262                }
263                else
264                        logPrintf("GenoOperators", "linearMix", LOG_WARN, "Cannot mix values of types '%c' and '%c'", type1, type2);
265}
266
267int GenoOperators::getActiveNeuroClassCount(Model::ShapeType for_shape_type)
268{
269        int count = 0;
270        for (int i = 0; i < Neuro::getClassCount(); i++)
271        {
272                NeuroClass *nc = Neuro::getClass(i);
273                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive)
274                        count++;
275        }
276        return count;
277}
278
279NeuroClass *GenoOperators::getRandomNeuroClass(Model::ShapeType for_shape_type)
280{
281        vector<NeuroClass *> active;
282        for (int i = 0; i < Neuro::getClassCount(); i++)
283        {
284                NeuroClass *nc = Neuro::getClass(i);
285                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive)
286                        active.push_back(nc);
287        }
288        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
289}
290
291NeuroClass *GenoOperators::getRandomNeuroClassWithOutput(Model::ShapeType for_shape_type)
292{
293        vector<NeuroClass *> active;
294        for (int i = 0; i < Neuro::getClassCount(); i++)
295        {
296                NeuroClass *nc = Neuro::getClass(i);
297                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive && nc->getPreferredOutput() != 0)
298                        active.push_back(nc);
299        }
300        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
301}
302
303NeuroClass *GenoOperators::getRandomNeuroClassWithInput(Model::ShapeType for_shape_type)
304{
305        vector<NeuroClass *> active;
306        for (int i = 0; i < Neuro::getClassCount(); i++)
307        {
308                NeuroClass *nc = Neuro::getClass(i);
309                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive && nc->getPreferredInputs() != 0)
310                        active.push_back(nc);
311        }
312        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
313}
314
315NeuroClass *GenoOperators::getRandomNeuroClassWithOutputAndNoInputs(Model::ShapeType for_shape_type)
316{
317        vector<NeuroClass *> active;
318        for (int i = 0; i < Neuro::getClassCount(); i++)
319        {
320                NeuroClass *nc = Neuro::getClass(i);
321                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive && nc->getPreferredOutput() != 0 && nc->getPreferredInputs() == 0)
322                        active.push_back(nc);
323        }
324        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
325}
326
327int GenoOperators::getRandomNeuroClassWithOutput(const vector<NeuroClass *> &NClist)
328{
329        vector<int> allowed;
330        for (size_t i = 0; i < NClist.size(); i++)
331                if (NClist[i]->getPreferredOutput() != 0) //this NeuroClass provides output
332                        allowed.push_back(i);
333        if (allowed.size() == 0) return -1; else return allowed[rndUint(allowed.size())];
334}
335
336int GenoOperators::getRandomNeuroClassWithInput(const vector<NeuroClass *> &NClist)
337{
338        vector<int> allowed;
339        for (size_t i = 0; i < NClist.size(); i++)
340                if (NClist[i]->getPreferredInputs() != 0) //this NeuroClass wants one input connection or more                 
341                        allowed.push_back(i);
342        if (allowed.size() == 0) return -1; else return allowed[rndUint(allowed.size())];
343}
344
345int GenoOperators::getRandomChar(const char *choices, const char *excluded)
346{
347        int allowed_count = 0;
348        for (size_t i = 0; i < strlen(choices); i++) if (!strchrn0(excluded, choices[i])) allowed_count++;
349        if (allowed_count == 0) return -1; //no char is allowed
350        int rnd_index = rndUint(allowed_count) + 1;
351        allowed_count = 0;
352        for (size_t i = 0; i < strlen(choices); i++)
353        {
354                if (!strchrn0(excluded, choices[i])) allowed_count++;
355                if (allowed_count == rnd_index) return i;
356        }
357        return -1; //never happens
358}
359
360NeuroClass *GenoOperators::parseNeuroClass(char *&s)
361{
362        int maxlen = (int)strlen(s);
363        int NClen = 0;
364        NeuroClass *NC = NULL;
365        for (int i = 0; i < Neuro::getClassCount(); i++)
366        {
367                const char *ncname = Neuro::getClass(i)->name.c_str();
368                int ncnamelen = (int)strlen(ncname);
369                if (maxlen >= ncnamelen && ncnamelen > NClen && (strncmp(s, ncname, ncnamelen) == 0))
370                {
371                        NC = Neuro::getClass(i);
372                        NClen = ncnamelen;
373                }
374        }
375        s += NClen;
376        return NC;
377}
378
379Neuro *GenoOperators::findNeuro(const Model *m, const NeuroClass *nc)
380{
381        if (!m) return NULL;
382        for (int i = 0; i < m->getNeuroCount(); i++)
383                if (m->getNeuro(i)->getClass() == nc) return m->getNeuro(i);
384        return NULL; //neuron of class 'nc' was not found
385}
386
387int GenoOperators::neuroClassProp(char *&s, NeuroClass *nc, bool also_v1_N_props)
388{
389        int len = (int)strlen(s);
390        int Len = 0, I = -1;
391        if (nc)
392        {
393                Param p = nc->getProperties();
394                for (int i = 0; i < p.getPropCount(); i++)
395                {
396                        const char *n = p.id(i);
397                        int l = (int)strlen(n);
398                        if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = NEUROCLASS_PROP_OFFSET + i; Len = l; }
399                        if (also_v1_N_props) //recognize old symbols of properties:  /=!
400                        {
401                                if (strcmp(n, "si") == 0) n = "/"; else
402                                        if (strcmp(n, "in") == 0) n = "="; else
403                                                if (strcmp(n, "fo") == 0) n = "!";
404                                l = (int)strlen(n);
405                                if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = NEUROCLASS_PROP_OFFSET + i; Len = l; }
406                        }
407                }
408        }
409        Neuro n;
410        Param p = n.extraProperties();
411        for (int i = 0; i < p.getPropCount(); i++)
412        {
413                const char *n = p.id(i);
414                int l = (int)strlen(n);
415                if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = i; Len = l; }
416        }
417        s += Len;
418        return I;
419}
420
421bool GenoOperators::isWS(const char c)
422{
423        return c == ' ' || c == '\n' || c == '\t' || c == '\r';
424}
425
426void GenoOperators::skipWS(char *&s)
427{
428        if (s == NULL)
429                logMessage("GenoOperators", "skipWS", LOG_WARN, "NULL reference!");
430        else
431                while (isWS(*s)) s++;
432}
433
434bool GenoOperators::areAlike(char *g1, char *g2)
435{
436        while (*g1 || *g2)
437        {
438                skipWS(g1);
439                skipWS(g2);
440                if (*g1 != *g2) return false; //when difference
441                if (!*g1 && !*g2) break; //both end
442                g1++;
443                g2++;
444        }
445        return true; //equal
446}
447
448char *GenoOperators::strchrn0(const char *str, char ch)
449{
450        return ch == 0 ? NULL : strchr((char *)str, ch);
451}
452
453bool GenoOperators::canStartNeuroClassName(const char firstchar)
454{
455        return isupper(firstchar) || firstchar == '|' || firstchar == '@' || firstchar == '*';
456}
Note: See TracBrowser for help on using the repository browser.