Changeset 1254


Ignore:
Timestamp:
06/22/23 03:29:05 (17 months ago)
Author:
Maciej Komosinski
Message:
  • turn -0.0 into 0.0 if we have minimum==0.0, so that it does not look as if we got a value below 0.0 (minimum) after mutation (even though -0.0==0.0)
  • implemented stochastic rounding for the mutation of integer parameters
Location:
cpp/frams/genetics
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • cpp/frams/genetics/genooperators.cpp

    r1247 r1254  
    55#include <ctype.h>  //isupper()
    66#include <algorithm> // std::min, std::max
     7#include <cmath> // std::floor()
    78#include "genooperators.h"
    89#include <common/log.h>
     
    203204                if (result > mx) result = mx - (result - mx); else
    204205                        if (result < mn) result = mn + (mn - result);
    205                 //wrap (just in case 'result' exceeded the allowed range so much that after reflection above it exceeded the other boundary):
     206                //wrap (just in case 'result' exceeded the allowed range so much that after the reflection above it exceeded the other boundary):
    206207                if (result > mx) result = mn + fmod(result - mx, mx - mn); else
    207208                        if (result < mn) result = mn + fmod(mn - result, mx - mn);
     
    210211                        //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)
    211212                        double result_try = round(result, 3);
    212                         if (mn <= result_try && result_try <= mx) result = result_try; //after rounding still witin allowed range, so keep rounded value
    213                 }
    214         }
     213                        if (mn <= result_try && result_try <= mx) result = result_try; //after rounding still within allowed range, so keep rounded value
     214                }
     215        }
     216        clipNegativeZeroIfNeeded(result, mn); //so we don't get -0.0 when minimum is 0.0
    215217        return result;
    216218}
     
    222224}
    223225
    224 void GenoOperators::setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value) //TODO
    225 {
    226         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!)
     226void GenoOperators::setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value)
     227{
     228        // Deterministic rounding to the closest integer:
     229        //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).
     230
     231        //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):
     232        double lower = std::floor(value);
     233        value = rndDouble(1) < (value - lower) ? lower + 1 : lower;
     234
     235        p.setInt(index, (paInt)value);
    227236}
    228237
  • cpp/frams/genetics/genooperators.h

    r1243 r1254  
    195195        static double mutateCreep(char type, double current, double mn, double mx, double stddev, bool limit_precision_3digits); ///<just as mutateCreepNoLimit(), but forces mutated value into the [mn,mx] range using the 'reflect' approach.
    196196        static double mutateCreep(char type, double current, double mn, double mx, bool limit_precision_3digits); ///<just as mutateCreepNoLimit(), but forces mutated value into the [\a mn,\a mx] range using the 'reflect' approach and assumes standard deviation to be a fraction of the mx-mn interval width.
    197         static void setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value); ///<sets a double value in an integer field; when a value is non-integer, applies random "dithering" so that both lower and higher integer value have some chance to be set.
     197        static void setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value); ///<sets a double value in an integer field; when a value is non-integer, applies stochastic rounding (random "dithering") so that both lower and higher integer value have some chance to be set.
    198198        static void linearMix(vector<double> &p1, vector<double> &p2, double proportion); ///<mixes two real-valued vectors; inherited proportion should be within [0,1]; 1.0 does not change values (all inherited), 0.5 causes both vectors to become their average, 0.0 swaps values (none inherited).
    199199        static void linearMix(ParamInterface &p1, int i1, ParamInterface &p2, int i2, double proportion); ///<mixes i1'th and i2'th properties of p1 and p2; inherited proportion should be within [0,1]; 1.0 does not change values (all inherited), 0.5 causes both properties to become their average, 0.0 swaps values (none inherited). For integer properties applies random "dithering" when necessary.
     
    221221        static string simplifiedModifiersFixedOrder(const char *str_of_char_pairs, vector<int> &char_counts); ///<returns a sequence of chars from \a str_of_char_pairs based on how many times each char occurred in \a char_counts. Assume that an even-index char and the following odd-index char have the opposite influence, so they cancel out. We don't use this function, because a fixed order imposed by this function means that the number of different parameter values produced by a sequence of modifiers is lowered (N same-letter upper- and lower-case chars yield only 2*N different values). Due to how modifiers work, the effect of aaA, aAa, Aaa etc. is different (N same-letter upper- and lower-case chars yield 2^N different values), so simplifying modifiers should not impose any order, should not interfere with their original order, and should not cancel out antagonistic modifiers - see \a simplifiedModifiers() and geneprops_test.cpp.
    222222        //@}
    223         static string simplifiedModifiers(const string &original); ///<from the \a original sequence removes modifiers that are too numerous (exceeding a defined threshold number), starting the removal from the leftmost (="oldest" when interpreting the sequence from left to right) ones.
     223        static string simplifiedModifiers(const string &original); ///<from the \a original sequence removes modifiers that are too numerous (exceeding a defined threshold number), starting the removal from the least-significant, leftmost (="oldest" when interpreting the sequence from left to right) ones. Contrary to \a simplifiedModifiersFixedOrder(), this kind of simplification preserves 2^N different sequences for each upper/lower-case modifier and thus 2^N different values of a given property (see geneprops.cpp), but the values resulting from these sequences constitute a landscape not as easy for optimization as in the case of 2*N, where the effect of each mutation could be independent and additive (no epistasis). So for a given sequence length, the 2^N case allows for a higher resolution at the cost of a more rugged fitness landscape than the 2*N case.
    224224};
    225225
Note: See TracChangeset for help on using the changeset viewer.