diff --git a/eo/src/eo b/eo/src/eo index bc8ed068..d2df9d51 100644 --- a/eo/src/eo +++ b/eo/src/eo @@ -115,6 +115,7 @@ // Perf2Worth #include +#include // Algorithms #include diff --git a/eo/src/eoDominanceMap.h b/eo/src/eoDominanceMap.h index d19e5755..582501be 100644 --- a/eo/src/eoDominanceMap.h +++ b/eo/src/eoDominanceMap.h @@ -93,12 +93,12 @@ class eoDominanceMap : public eoUF&, void>, public std::vect for (unsigned j = 0; j < i; ++j) { - if (_pop[i].fitness() > _pop[j].fitness()) + if (_pop[i].fitness().dominates(_pop[j].fitness())) { operator[](i)[j] = true; operator[](j)[i] = false; } - else if (_pop[j].fitness() > _pop[i].fitness()) + else if (_pop[j].fitness().dominates(_pop[i].fitness())) { operator[](i)[j] = false; operator[](j)[i] = true; diff --git a/eo/src/eoLinearFitScaling.h b/eo/src/eoLinearFitScaling.h index b5c0d8aa..424ebda7 100644 --- a/eo/src/eoLinearFitScaling.h +++ b/eo/src/eoLinearFitScaling.h @@ -35,12 +35,12 @@ * with given selective pressure * Pselect(Best) == pressure/sizePop * Pselect(average) == 1.0/sizePop - * truncate negative values to 0 - + * truncate negative values to 0 - * * to be used within an eoSelectFromWorth object */ template -class eoLinearFitScaling : public eoPerf2Worth +class eoLinearFitScaling : public eoPerf2Worth // false: do not cache fitness { public: /* Ctor: @@ -50,7 +50,7 @@ public: eoLinearFitScaling(double _p=2.0): pressure(_p) {} - /* COmputes the ranked fitness: fitnesses range in [m,M] + /* COmputes the ranked fitness: fitnesses range in [m,M] with m=2-pressure/popSize and M=pressure/popSize. in between, the progression depends on exponent (linear if 1). */ diff --git a/eo/src/eoNDSorting.h b/eo/src/eoNDSorting.h index 4b139060..0193d8aa 100644 --- a/eo/src/eoNDSorting.h +++ b/eo/src/eoNDSorting.h @@ -29,7 +29,6 @@ #include #include -#include /** Non dominated sorting, it *is a* vector of doubles, the integer part is the rank (to which front it belongs), @@ -37,63 +36,88 @@ the bits. */ template -class eoNDSorting : public eoPerf2Worth +class eoNDSorting : public eoPerf2WorthCached { public : - eoNDSorting(eoDominanceMap& _dominanceMap) : - eoPerf2Worth(), dominanceMap(_dominanceMap) {} - /** Pure virtual function that calculates the 'distance' for each element to the current front Implement to create your own nondominated sorting algorithm. The size of the returned vector should be equal to the size of the current_front. */ virtual vector niche_penalty(const vector& current_front, const eoPop& _pop) = 0; - /// Do the work - void operator()(const eoPop& _pop) + /** implements fast nondominated sorting + */ + void calculate_worths(const eoPop& _pop) { - dominanceMap(_pop); - - vector excluded(_pop.size(), false); - value().resize(_pop.size()); - bool finished = false; + typedef typename EOT::Fitness::fitness_traits traits; - int dominance_level = 0; - - while(!finished) - { - vector ranks = dominanceMap.sum_dominators(); - - vector current_front; - current_front.reserve(_pop.size()); - - finished = true; + if (traits::nObjectives() == 1) + { // no need to do difficult sorting, for (unsigned i = 0; i < _pop.size(); ++i) { - if (excluded[i]) - { - continue; // next please - } + value()[i] = _pop[i].fitness()[0]; + } - if (ranks[i] < 1e-6) - {// it's part of the current front - excluded[i] = true; - current_front.push_back(i); - dominanceMap.remove(i); // remove from consideration - } - else + if (!traits::maximizing(0)) + { + for (unsigned i = 0; i < value().size(); ++i) { - finished = false; // we need another go + value()[i] = exp(-value()[i]); } } + return; + } + + vector > S(_pop.size()); // which individuals does guy i dominate + vector n(_pop.size(), 0); // how many individuals dominate guy i + + + for (unsigned i = 0; i < _pop.size(); ++i) + { + for (unsigned j = 0; j < _pop.size(); ++j) + { + if (_pop[i].fitness().dominates(_pop[j].fitness())) + { // i dominates j + S[i].push_back(j); // add j to i's domination list + + //n[j]++; // as i dominates j + } + else if (_pop[j].fitness().dominates(_pop[i].fitness())) + { // j dominates i, increment count for i, add i to the domination list of j + n[i]++; + + //S[j].push_back(i); + } + } + } + + vector current_front; + current_front.reserve(_pop.size()); + + // get the first front out + for (unsigned i = 0; i < _pop.size(); ++i) + { + if (n[i] == 0) + { + current_front.push_back(i); + } + } + + vector next_front; + next_front.reserve(_pop.size()); + + unsigned front_index = 0; // which front are we processing + while (!current_front.empty()) + { // Now we have the indices to the current front in current_front, do the niching vector niche_count = niche_penalty(current_front, _pop); + // Check whether the derived class was nice if (niche_count.size() != current_front.size()) { throw logic_error("eoNDSorting: niche and front should have the same size"); @@ -103,10 +127,28 @@ class eoNDSorting : public eoPerf2Worth for (unsigned i = 0; i < current_front.size(); ++i) { - value()[current_front[i]] = dominance_level + niche_count[i] / (max_niche + 1); + value()[current_front[i]] = front_index + niche_count[i] / (max_niche + 1.); // divide by max_niche + 1 to ensure that this front does not overlap with the next } - dominance_level++; // go to the next front + // Calculate which individuals are in the next front; + + for (unsigned i = 0; i < current_front.size(); ++i) + { + for (unsigned j = 0; j < S[current_front[i]].size(); ++j) + { + unsigned dominated_individual = S[current_front[i]][j]; + n[dominated_individual]--; // As we remove individual i -- being part of the current front -- it no longer dominates j + + if (n[dominated_individual] == 0) // it should be in the current front + { + next_front.push_back(dominated_individual); + } + } + } + + front_index++; // go to the next front + swap(current_front, next_front); // make the next front current + next_front.clear(); // clear it for the next iteration } // now all that's left to do is to transform lower rank into higher worth @@ -115,15 +157,10 @@ class eoNDSorting : public eoPerf2Worth for (unsigned i = 0; i < value().size(); ++i) { value()[i] = max_fitness - value()[i]; + assert(n[i] == 0); } } - - const eoDominanceMap& map() const; - - private : - - eoDominanceMap& dominanceMap; }; /** @@ -133,7 +170,7 @@ template class eoNDSorting_I : public eoNDSorting { public : - eoNDSorting_I(eoDominanceMap& _map, double _nicheSize) : eoNDSorting(_map), nicheSize(_nicheSize) {} + eoNDSorting_I(double _nicheSize) : eoNDSorting(), nicheSize(_nicheSize) {} vector niche_penalty(const vector& current_front, const eoPop& _pop) { @@ -182,7 +219,6 @@ template class eoNDSorting_II : public eoNDSorting { public: - eoNDSorting_II(eoDominanceMap& _map) : eoNDSorting(_map) {} typedef std::pair double_index_pair; @@ -222,9 +258,9 @@ class eoNDSorting_II : public eoNDSorting double max_dist = *max_element(nc.begin(), nc.end()); - // set boundary penalty at 0 (so it will get chosen over all the others - nc[performance[0].second] = 0; - nc[performance.back().second] = 0; + // set boundary at max_dist + 1 (so it will get chosen over all the others + nc[performance[0].second] = max_dist + 1; + nc[performance.back().second] = max_dist + 1; for (unsigned i = 0; i < nc.size(); ++i) { diff --git a/eo/src/eoParetoFitness.h b/eo/src/eoParetoFitness.h index 4e4b210b..37b20f7f 100644 --- a/eo/src/eoParetoFitness.h +++ b/eo/src/eoParetoFitness.h @@ -56,12 +56,15 @@ class eoParetoFitness : public std::vector { public : + typedef FitnessTraits fitness_traits; + eoParetoFitness(void) : std::vector(FitnessTraits::nObjectives(),0.0) {} /// Partial order based on Pareto-dominance - bool operator<(const eoParetoFitness& _other) const + //bool operator<(const eoParetoFitness& _other) const + bool dominates(const eoParetoFitness& _other) const { - bool smaller = false; + bool dom = false; double tol = FitnessTraits::tol(); const vector& performance = *this; @@ -75,17 +78,41 @@ public : if (fabs(aval - bval) > tol) { - if (aval > bval) + if (aval < bval) { return false; // cannot dominate } // else aval < bval - smaller = true; // goto next objective + dom = true; // for the moment: goto next objective } //else they're equal in this objective, goto next } - return smaller; + return dom; + } + + /// compare *not* on dominance, but on the first, then the second, etc + bool operator<(const eoParetoFitness& _other) const + { + double tol = FitnessTraits::tol(); + const vector& performance = *this; + const vector& otherperformance = _other; + for (unsigned i = 0; i < FitnessTraits::nObjectives(); ++i) + { + bool maxim = FitnessTraits::maximizing(i); + double aval = maxim? performance[i] : -performance[i]; + double bval = maxim? otherperformance[i] : -otherperformance[i]; + + if (fabs(aval-bval) > tol) + { + if (aval < bval) + return true; + + return false; + } + } + + return false; } bool operator>(const eoParetoFitness& _other) const diff --git a/eo/src/eoParetoRanking.h b/eo/src/eoParetoRanking.h index c73f1ef1..322c97ba 100644 --- a/eo/src/eoParetoRanking.h +++ b/eo/src/eoParetoRanking.h @@ -37,17 +37,27 @@ on a single spot on the front. */ template -class eoParetoRanking : public eoPerf2Worth +class eoParetoRanking : public eoPerf2WorthCached { public : eoParetoRanking(eoDominanceMap& _dominanceMap) : - eoPerf2Worth(), dominanceMap(_dominanceMap) {} + eoPerf2WorthCached(), dominanceMap(_dominanceMap) {} - void operator()(const eoPop& _pop) + void calculate_worths(const eoPop& _pop) { dominanceMap(_pop); - value() = dominanceMap.sum_dominants(); + value() = dominanceMap.sum_dominators(); // get rank: 0 means part of current front + + // calculate maximum + double maxim = *max_element(value().begin(), value().end()); + + // higher is better, so invert the value + for (unsigned i = 0; i < value().size(); ++i) + { + value()[i] = maxim - value()[i]; + } + } private : diff --git a/eo/src/eoPerf2Worth.h b/eo/src/eoPerf2Worth.h index 3ad20358..21ea551a 100644 --- a/eo/src/eoPerf2Worth.h +++ b/eo/src/eoPerf2Worth.h @@ -1,9 +1,9 @@ /** -*- mode: c++; c-indent-level: 4; c++-member-init-indent: 8; comment-column: 35; -*- ----------------------------------------------------------------------------- - eoPerf2Worth.h + eoPerf2Worth.h (c) Maarten Keijzer, Marc Schoenauer, 2001 - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -27,22 +27,198 @@ #ifndef eoPerf2Worth_h #define eoPerf2Worth_h -#include +#include +#include /** Base class to transform raw fitnesses into fitness for selection -It is an eoStat so -- it is updated inside a checkpoint (i.e. at start of every generation) -- it can be monitored or whatever else you wish through its value() - @see eoSelectFromWorth */ -template -class eoPerf2Worth : public eoStat > +template +class eoPerf2Worth : public eoUF&, void>, public eoValueParam > { public: - eoPerf2Worth(std::string _description = "Worths"):eoStat >(vector(0), + eoPerf2Worth(std::string _description = "Worths"):eoValueParam >(vector(0), _description) {} + + /** + Sort population according to worth, will keep the worths and fitness_cache in sync with the population. + */ + virtual void sort_pop(eoPop& _pop) + { // start with a vector of indices + vector indices(_pop.size()); + + for (unsigned i = 0; i < _pop.size();++i) + { // could use generate, but who cares + indices[i] = i; + } + + sort(indices.begin(), indices.end(), compare_worth(value())); + + eoPop tmp_pop; + tmp_pop.resize(_pop.size()); + vector tmp_worths(value().size()); + + for (unsigned i = 0; i < _pop.size(); ++i) + { + tmp_pop[i] = _pop[indices[i]]; + tmp_worths[i] = value()[indices[i]]; + } + + swap(_pop, tmp_pop); + swap(value(), tmp_worths); + } + + /** helper class used to sort indices into populations/worths + */ + class compare_worth + { + public : + compare_worth(const vector& _worths) : worths(_worths) {} + + bool operator()(unsigned a, unsigned b) const + { + return worths[b] < worths[a]; // sort in descending (!) order + } + + private : + + const vector& worths; + }; + + virtual void resize(eoPop& _pop, unsigned sz) + { + _pop.resize(sz); + value().resize(sz); + } + +}; + +/** +Perf2Worth with fitness cache +*/ +template +class eoPerf2WorthCached : public eoPerf2Worth +{ + public: + eoPerf2WorthCached(std::string _description = "Worths") : eoPerf2Worth(_description) {} + + + /** + Implementation of the operator(), updating a cache of fitnesses. Calls the virtual function + calculate_worths when one of the fitnesses has changed. It is not virtual, but derived classes + can remove the fitness caching trough the third template element + */ + void operator()(const eoPop& _pop) + { + if (fitness_cache.size() == _pop.size()) + { + bool in_sync = true; + for (unsigned i = 0; i < _pop.size(); ++i) + { + if (fitness_cache[i] != _pop[i].fitness()) + { + in_sync = false; + fitness_cache[i] = _pop[i].fitness(); + } + } + + if (in_sync) + { // worths are up to date + return; + } + } + else // just cache the fitness + { + fitness_cache.resize(_pop.size()); + for (unsigned i = 0; i < _pop.size(); ++i) + { + fitness_cache[i] = _pop[i].fitness(); + } + } + + // call derived implementation of perf2worth mapping + calculate_worths(_pop); + } + + /** The actual virtual function the derived classes should implement*/ + virtual void calculate_worths(const eoPop& _pop) = 0; + + /** + Sort population according to worth, will keep the worths and fitness_cache in sync with the population. + */ + virtual void sort_pop(eoPop& _pop) + { // start with a vector of indices + vector indices(_pop.size()); + + for (unsigned i = 0; i < _pop.size();++i) + { // could use generate, but who cares + indices[i] = i; + } + + sort(indices.begin(), indices.end(), eoPerf2Worth::compare_worth(value())); + + eoPop tmp_pop; + tmp_pop.resize(_pop.size()); + vector tmp_worths(value().size()); + vector tmp_cache(_pop.size()); + + for (unsigned i = 0; i < _pop.size(); ++i) + { + tmp_pop[i] = _pop[indices[i]]; + tmp_worths[i] = value()[indices[i]]; + + tmp_cache[i] = fitness_cache[indices[i]]; + } + + swap(_pop, tmp_pop); + swap(value(), tmp_worths); + swap(fitness_cache, tmp_cache); + } + + /** helper class used to sort indices into populations/worths + */ + class compare_worth + { + public : + compare_worth(const vector& _worths) : worths(_worths) {} + + bool operator()(unsigned a, unsigned b) const + { + return worths[b] < worths[a]; // sort in descending (!) order + } + + private : + + const vector& worths; + }; + + virtual void resize(eoPop& _pop, unsigned sz) + { + _pop.resize(sz); + value().resize(sz); + fitness_cache.resize(sz); + } + + private : + vector fitness_cache; +}; + +/** + A dummy perf2worth, just in case you need it +*/ +template +class eoNoPerf2Worth : public eoPerf2Worth +{ + public: + + // default behaviour, just copy fitnesses + void operator()(const eoPop& _pop) + { + value.resize(_pop.size()); + for (unsigned i = 0; i < _pop.size(); ++i) + value()[i]=_pop[i]; + } }; #endif diff --git a/eo/src/eoPop.h b/eo/src/eoPop.h index 3634c21e..8731258c 100644 --- a/eo/src/eoPop.h +++ b/eo/src/eoPop.h @@ -42,7 +42,7 @@ some other thing that a vector, but if somebody thinks of it, this concrete implementation will be moved to "generic" and an abstract Population interface will be provided. -It can be instantiated with anything, provided that it accepts a "size" and a +It can be instantiated with anything, provided that it accepts a "size" and a random generator in the ctor. This happens to all the eo1d chromosomes declared so far. EOT must also have a copy ctor, since temporaries are created and copied to the population. diff --git a/eo/src/eoRanking.h b/eo/src/eoRanking.h index 37fd5f64..24edde2e 100644 --- a/eo/src/eoRanking.h +++ b/eo/src/eoRanking.h @@ -1,9 +1,9 @@ /** -*- mode: c++; c-indent-level: 4; c++-member-init-indent: 8; comment-column: 35; -*- ----------------------------------------------------------------------------- - eoRanking.h + eoRanking.h (c) Maarten Keijzer, Marc Schoenauer, 2001 - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -30,12 +30,12 @@ #include /** An instance of eoPerfFromWorth - * COmputes the ranked fitness: fitnesses range in [m,M] + * COmputes the ranked fitness: fitnesses range in [m,M] * with m=2-pressure/popSize and M=pressure/popSize. * in between, the progression depends on exponent (linear if 1). */ template -class eoRanking : public eoPerf2Worth +class eoRanking : public eoPerf2Worth // false: do not cache fitness { public: /* Ctor: @@ -57,7 +57,7 @@ public: throw runtime_error("Not found in eoLinearRanking"); } - /* COmputes the ranked fitness: fitnesses range in [m,M] + /* COmputes the ranked fitness: fitnesses range in [m,M] with m=2-pressure/popSize and M=pressure/popSize. in between, the progression depends on exponent (linear if 1). */ diff --git a/eo/src/eoSelectOne.h b/eo/src/eoSelectOne.h index b6965580..29857034 100644 --- a/eo/src/eoSelectOne.h +++ b/eo/src/eoSelectOne.h @@ -31,19 +31,20 @@ #include //----------------------------------------------------------------------------- -/** eoSelectOne selects only one element from a whole population. +/** eoSelectOne selects only one element from a whole population. Most selection techniques are simply repeated applications of eoSelectOne. - + @see eoSelectMany, eoSelectRandom, eoDetTournament, eoStochTournament, eoProportional */ -template +template class eoSelectOne : public eoUF&, const EOT&> { public : - - /// virtual function to setup some population stats (for instance eoProportional can benefit greatly from this) - virtual void setup(const eoPop&) {} + /// virtual function to setup some population stats (for instance eoProportional can benefit greatly from this) + virtual void setup(const eoPop& _pop) + {} }; + #endif