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

Last change on this file since 968 was 968, checked in by Maciej Komosinski, 4 years ago

Introduced a separate function to mutate neuron connection weight

  • Property svn:eol-style set to native
File size: 14.3 KB
RevLine 
[286]1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
[935]2// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
[286]3// See LICENSE.txt for details.
[109]4
5#include <ctype.h>  //isupper()
[779]6#include "genooperators.h"
[375]7#include <common/log.h>
[109]8#include <common/nonstd_math.h>
9#include <frams/util/rndutil.h>
10
[968]11//
12// custom distributions for mutations of various parameters
13//
[168]14static double distrib_force[] =   // for '!'
[109]15{
[168]16        3,             // distribution 0 -__/ +1
17        0.001, 0.2,    // "slow" neurons
18        0.001, 1,
19        1, 1,          // "fast" neurons
[109]20};
[168]21static double distrib_inertia[] =  // for '='
[109]22{
[168]23        2,             // distribution 0 |..- +1
24        0, 0,          // "fast" neurons
25        0.7, 0.98,
[109]26};
[168]27static double distrib_sigmo[] =  // for '/'
[109]28{
[168]29        5,             // distribution -999 -..-^-..- +999
30        -999, -999,    //"perceptron"
31        999, 999,
32        -5, -1,        // nonlinear
33        1, 5,
34        -1, 1,         // ~linear
[109]35};
[968]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*/
[109]47
[168]48int GenoOperators::roulette(const double *probtab, const int count)
[109]49{
[168]50        double sum = 0;
51        int i;
52        for (i = 0; i < count; i++) sum += probtab[i];
[896]53        double sel = rndDouble(sum);
[168]54        for (sum = 0, i = 0; i < count; i++) { sum += probtab[i]; if (sel < sum) return i; }
55        return -1;
[109]56}
57
[168]58bool GenoOperators::getMinMaxDef(ParamInterface *p, int i, double &mn, double &mx, double &def)
[109]59{
[168]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;
[743]65                defined = p->getMinMaxDouble(i, _mn, _mx, _def);
[765]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                }
[168]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        {
[247]77                paInt _mn = 0, _mx = 1, _def = 0;
[743]78                defined = p->getMinMaxInt(i, _mn, _mx, _def);
[765]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                }
[168]85                if (defined < 3) _def = (_mn + _mx) / 2;
86                mn = _mn; mx = _mx; def = _def;
87        }
88        return defined == 3;
[109]89}
90
[967]91bool GenoOperators::mutateRandomNeuroClassProperty(Neuro* n)
[959]92{
93        bool mutated = false;
[967]94        int prop = selectRandomNeuroClassProperty(n);
[959]95        if (prop >= 0)
96        {
97                if (prop >= GenoOperators::NEUROCLASS_PROP_OFFSET)
98                {
[967]99                        SyntParam par = n->classProperties();   //commits changes when this object is destroyed
[959]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
[967]111int GenoOperators::selectRandomNeuroClassProperty(Neuro *n)
[109]112{
[168]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
[896]116        int index = rndUint(neuext + neucls);
[957]117        if (index >= neuext) index = index - neuext + NEUROCLASS_PROP_OFFSET;
[168]118        return index;
[109]119}
120
[967]121double GenoOperators::getMutatedNeuroClassProperty(double current, Neuro *n, int i)
[109]122{
[968]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        }
[168]128        Param p;
[957]129        if (i >= NEUROCLASS_PROP_OFFSET) { i -= NEUROCLASS_PROP_OFFSET; p = n->getClass()->getProperties(); }
[168]130        else p = n->extraProperties();
131        double newval = current;
132        /*bool ok=*/getMutatedProperty(p, i, current, newval);
133        return newval;
[109]134}
135
[968]136double GenoOperators::getMutatedNeuronConnectionWeight(double current)
137{
138        return mutateCreepNoLimit('f', current, 2, true);
139}
140
[168]141bool GenoOperators::mutatePropertyNaive(ParamInterface &p, int i)
[109]142{
[168]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);
[109]146
[168]147        ExtValue ev;
148        p.get(i, ev);
[751]149        ev.setDouble(mutateCreep(p.type(i)[0], ev.getDouble(), mn, mx, true));
[168]150        p.set(i, ev);
151        return true;
[109]152}
153
[168]154bool GenoOperators::mutateProperty(ParamInterface &p, int i)
[109]155{
[168]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;
[109]162}
163
[168]164bool GenoOperators::getMutatedProperty(ParamInterface &p, int i, double oldval, double &newval)
[109]165{
[168]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);
[968]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
[168]172                        {
[899]173                                double mn, mx, df;
174                                getMinMaxDef(&p, i, mn, mx, df);
175                                newval = mutateCreep(p.type(i)[0], oldval, mn, mx, true);
[168]176                        }
177        return true;
[109]178}
179
[751]180double GenoOperators::mutateCreepNoLimit(char type, double current, double stddev, bool limit_precision_3digits)
[109]181{
[751]182        double result = RndGen.Gauss(current, stddev);
183        if (type == 'd')
184        {
185                result = int(result + 0.5);
[896]186                if (result == current) result += rndUint(2) * 2 - 1; //force some change
[751]187        }
188        else
189        {
190                if (limit_precision_3digits)
[968]191                        result = round(result, 3);
[751]192        }
[168]193        return result;
[109]194}
195
[751]196double GenoOperators::mutateCreep(char type, double current, double mn, double mx, double stddev, bool limit_precision_3digits)
[109]197{
[751]198        double result = mutateCreepNoLimit(type, current, stddev, limit_precision_3digits);
[764]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)
[968]210                        double result_try = round(result, 3);
[764]211                        if (mn <= result_try && result_try <= mx) result = result_try; //after rounding still witin allowed range, so keep rounded value
212                }
213        }
[146]214        return result;
[109]215}
216
[751]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
[146]223void GenoOperators::setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value) //TODO
224{
[749]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!)
[146]226}
227
[749]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];
[899]239                p1[i] = v1 * proportion + v2 * (1 - proportion);
240                p2[i] = v2 * proportion + v1 * (1 - proportion);
[749]241        }
242}
243
[146]244void GenoOperators::linearMix(ParamInterface &p1, int i1, ParamInterface &p2, int i2, double proportion)
245{
[158]246        char type1 = p1.type(i1)[0];
247        char type2 = p2.type(i2)[0];
248        if (type1 == 'f' && type2 == 'f')
[146]249        {
250                double v1 = p1.getDouble(i1);
251                double v2 = p2.getDouble(i2);
[899]252                p1.setDouble(i1, v1 * proportion + v2 * (1 - proportion));
253                p2.setDouble(i2, v2 * proportion + v1 * (1 - proportion));
[146]254        }
[158]255        else
256                if (type1 == 'd' && type2 == 'd')
257                {
[899]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));
[158]262                }
263                else
[375]264                        logPrintf("GenoOperators", "linearMix", LOG_WARN, "Cannot mix values of types '%c' and '%c'", type1, type2);
[146]265}
266
[935]267int GenoOperators::getActiveNeuroClassCount(Model::ShapeType for_shape_type)
[801]268{
269        int count = 0;
270        for (int i = 0; i < Neuro::getClassCount(); i++)
[935]271        {
272                NeuroClass *nc = Neuro::getClass(i);
273                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive)
[801]274                        count++;
[935]275        }
[801]276        return count;
277}
278
[935]279NeuroClass *GenoOperators::getRandomNeuroClass(Model::ShapeType for_shape_type)
[109]280{
[899]281        vector<NeuroClass *> active;
[168]282        for (int i = 0; i < Neuro::getClassCount(); i++)
[935]283        {
284                NeuroClass *nc = Neuro::getClass(i);
285                if (nc->isShapeTypeSupported(for_shape_type) && nc->genactive)
286                        active.push_back(nc);
287        }
[896]288        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
[109]289}
290
[935]291NeuroClass *GenoOperators::getRandomNeuroClassWithOutput(Model::ShapeType for_shape_type)
[758]292{
[899]293        vector<NeuroClass *> active;
[758]294        for (int i = 0; i < Neuro::getClassCount(); i++)
[935]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        }
[896]300        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
[758]301}
302
[935]303NeuroClass *GenoOperators::getRandomNeuroClassWithInput(Model::ShapeType for_shape_type)
[758]304{
[899]305        vector<NeuroClass *> active;
[758]306        for (int i = 0; i < Neuro::getClassCount(); i++)
[935]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        }
[896]312        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
[758]313}
314
[935]315NeuroClass *GenoOperators::getRandomNeuroClassWithOutputAndNoInputs(Model::ShapeType for_shape_type)
[758]316{
[899]317        vector<NeuroClass *> active;
[758]318        for (int i = 0; i < Neuro::getClassCount(); i++)
[935]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        }
[896]324        if (active.size() == 0) return NULL; else return active[rndUint(active.size())];
[758]325}
326
[899]327int GenoOperators::getRandomNeuroClassWithOutput(const vector<NeuroClass *> &NClist)
[673]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);
[896]333        if (allowed.size() == 0) return -1; else return allowed[rndUint(allowed.size())];
[673]334}
335
[899]336int GenoOperators::getRandomNeuroClassWithInput(const vector<NeuroClass *> &NClist)
[673]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);
[896]342        if (allowed.size() == 0) return -1; else return allowed[rndUint(allowed.size())];
[673]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
[896]350        int rnd_index = rndUint(allowed_count) + 1;
[673]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
[899]360NeuroClass *GenoOperators::parseNeuroClass(char *&s)
[109]361{
[670]362        int maxlen = (int)strlen(s);
363        int NClen = 0;
364        NeuroClass *NC = NULL;
[899]365        for (int i = 0; i < Neuro::getClassCount(); i++)
[168]366        {
[670]367                const char *ncname = Neuro::getClass(i)->name.c_str();
368                int ncnamelen = (int)strlen(ncname);
[899]369                if (maxlen >= ncnamelen && ncnamelen > NClen && (strncmp(s, ncname, ncnamelen) == 0))
[670]370                {
371                        NC = Neuro::getClass(i);
372                        NClen = ncnamelen;
373                }
[168]374        }
[670]375        s += NClen;
376        return NC;
[109]377}
378
[899]379Neuro *GenoOperators::findNeuro(const Model *m, const NeuroClass *nc)
[109]380{
[168]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
[109]385}
386
[899]387int GenoOperators::neuroClassProp(char *&s, NeuroClass *nc, bool also_v1_N_props)
[109]388{
[247]389        int len = (int)strlen(s);
[168]390        int Len = 0, I = -1;
391        if (nc)
392        {
393                Param p = nc->getProperties();
[899]394                for (int i = 0; i < p.getPropCount(); i++)
[168]395                {
396                        const char *n = p.id(i);
[247]397                        int l = (int)strlen(n);
[957]398                        if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = NEUROCLASS_PROP_OFFSET + i; Len = l; }
[968]399                        if (also_v1_N_props) //recognize old symbols of properties:  /=!
[168]400                        {
401                                if (strcmp(n, "si") == 0) n = "/"; else
402                                        if (strcmp(n, "in") == 0) n = "="; else
403                                                if (strcmp(n, "fo") == 0) n = "!";
[247]404                                l = (int)strlen(n);
[957]405                                if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = NEUROCLASS_PROP_OFFSET + i; Len = l; }
[168]406                        }
407                }
408        }
409        Neuro n;
410        Param p = n.extraProperties();
[899]411        for (int i = 0; i < p.getPropCount(); i++)
[168]412        {
413                const char *n = p.id(i);
[247]414                int l = (int)strlen(n);
[899]415                if (len >= l && l > Len && (strncmp(s, n, l) == 0)) { I = i; Len = l; }
[168]416        }
417        s += Len;
418        return I;
[109]419}
420
[121]421bool GenoOperators::isWS(const char c)
[168]422{
423        return c == ' ' || c == '\n' || c == '\t' || c == '\r';
424}
[109]425
[121]426void GenoOperators::skipWS(char *&s)
[158]427{
[168]428        if (s == NULL)
[375]429                logMessage("GenoOperators", "skipWS", LOG_WARN, "NULL reference!");
[158]430        else
[670]431                while (isWS(*s)) s++;
[109]432}
433
[168]434bool GenoOperators::areAlike(char *g1, char *g2)
[109]435{
436        while (*g1 || *g2)
437        {
438                skipWS(g1);
439                skipWS(g2);
440                if (*g1 != *g2) return false; //when difference
[168]441                if (!*g1 && !*g2) break; //both end
442                g1++;
443                g2++;
[109]444        }
445        return true; //equal
446}
447
[899]448char *GenoOperators::strchrn0(const char *str, char ch)
[168]449{
[899]450        return ch == 0 ? NULL : strchr((char *)str, ch);
[168]451}
[109]452
[758]453bool GenoOperators::canStartNeuroClassName(const char firstchar)
[109]454{
[168]455        return isupper(firstchar) || firstchar == '|' || firstchar == '@' || firstchar == '*';
[109]456}
Note: See TracBrowser for help on using the repository browser.