/* 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; version 2 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA © 2020 Thales group Authors: Johann Dreo */ #ifndef _eoAlgoFoundryFastGA_H_ #define _eoAlgoFoundryFastGA_H_ #include #include #include /** A class that assemble an eoFastGA on the fly, given a combination of available operators. * * The foundry should first be set up with sets of operators/parameters * for the main modules of a FastGA: * continuators, crossovers (and rate of call), mutations (and rate of call), * selections, replacement operators, offspring size, etc. * * This is done through public member variable's `add` method, * which takes the class name as template and its constructor's parameters * as arguments. For example: * @code * foundry.selectors.add< eoRandomSelect >(); * @endcode * * @warning If the constructor takes a reference YOU SHOULD ABSOLUTELY wrap it * in a `std::ref`, or it will silently be passed as a copy, * which would effectively disable any link with other operator(s). * * In a second step, the operators to be used should be selected * by indicating their wanted index or value, passing an array of 10 elements: * @code * foundry.select({ * double{0.1}, // crossover rate * size_t{1}, // crossover selector * size_t{2}, // crossover * size_t{3}, // selector after crossover * double{0.4}, // mutation rate * size_t{5}, // mutation selector * size_t{6}, // mutation * size_t{7}, // replacement * size_t{8}, // continuator * size_t{9} // nb of offsprings * }); * @endcode * * @note by default, the firsts of the 10 operators are selected. * * If you don't (want to) recall the order of the operators in the encoding, * you can use the `index()` member, for example: * @code * foundry.at(foundry.continuators.index()) = size_t{2}; // select the third continuator * @endcode * * Now, you can call the foundry just like any eoAlgo, by passing it an eoPop: * @code * foundry(pop); * @endcode * * It will instantiate the needed operators (only) and the algorithm itself on-the-fly, * and then run it. * * @note Thanks to the underlying eoOperatorFoundry, not all the added operators are instantiated. * Every instantiation is deferred upon actual use. That way, you can still reconfigure them * at any time with `eoForgeOperator::setup`, for example: * @code * foundry.selector.at(0).setup(0.5); // Will call constructor's arguments * @endcode * * @ingroup Foundry * @ingroup Algorithms */ template class eoAlgoFoundryFastGA : public eoAlgoFoundry { public: /** The constructon only take an eval, because all other operators * are stored in the public containers. */ eoAlgoFoundryFastGA( eoInit & init, eoEvalFunc& eval, const size_t max_evals = 10000, const size_t max_restarts = std::numeric_limits::max() ) : eoAlgoFoundry(10), crossover_rates(0, 0.0, 1.0), crossover_selectors(1, false), crossovers(2, false), aftercross_selectors(3, false), mutation_rates(4, 0.0, 1.0), mutation_selectors(5, false), mutations(6, false), replacements(7, false), continuators(8, true), // Always re-instantiate continuators, because they hold a state. offspring_sizes(9, 0, std::numeric_limits::max()), _eval(eval), _init(init), _max_evals(max_evals), _max_restarts(max_restarts) { } public: /* Operators containers @{ */ eoParameterFoundry< double > crossover_rates; eoOperatorFoundry< eoSelectOne > crossover_selectors; eoOperatorFoundry< eoQuadOp > crossovers; eoOperatorFoundry< eoSelectOne > aftercross_selectors; eoParameterFoundry< double > mutation_rates; eoOperatorFoundry< eoSelectOne > mutation_selectors; eoOperatorFoundry< eoMonOp > mutations; eoOperatorFoundry< eoReplacement > replacements; eoOperatorFoundry< eoContinue > continuators; eoParameterFoundry< size_t > offspring_sizes; /* @} */ /** instantiate and call the pre-selected algorithm. */ void operator()(eoPop& pop) { assert( crossover_selectors.size() > 0); assert(this->rank( crossover_selectors) < crossover_selectors.size()); assert( crossovers.size() > 0); assert(this->rank( crossovers) < crossovers.size()); assert(aftercross_selectors.size() > 0); assert(this->rank(aftercross_selectors) < aftercross_selectors.size()); assert( mutation_selectors.size() > 0); assert(this->rank( mutation_selectors) < mutation_selectors.size()); assert( mutations.size() > 0); assert(this->rank( mutations) < mutations.size()); assert( replacements.size() > 0); assert(this->rank( replacements) < replacements.size()); assert( continuators.size() > 0); assert(this->rank( continuators) < continuators.size()); // Objective function calls counter eoEvalCounterThrowException eval(_eval, _max_evals); eo::log << eo::xdebug << "Evaluations: " << eval.value() << " / " << _max_evals << std::endl; eoPopLoopEval pop_eval(eval); // Algorithm itself eoFastGA algo( this->crossover_rate(), this->crossover_selector(), this->crossover(), this->aftercross_selector(), this->mutation_rate(), this->mutation_selector(), this->mutation(), pop_eval, this->replacement(), this->continuator(), this->offspring_size() ); // Restart wrapper // eoAlgoPopReset reset_pop(_init, pop_eval); // eoGenContinue restart_cont(_max_restarts); // eoAlgoRestart restart(eval, algo, restart_cont, reset_pop); try { // restart(pop); algo(pop); } catch(eoMaxEvalException & e) { #ifndef NDEBUG eo::log << eo::debug << "Reached maximum evaluations: " << eval.getValue() << " / " << _max_evals << std::endl; #endif // In case some solutions were not evaluated when max eval occured. // FIXME can this even be considered legal? // eoPopLoopEval pop_last_eval(_eval); // pop_last_eval(pop,pop); } } /** Return an approximate name of the selected algorithm. */ std::string name() { std::ostringstream name; name << "crossover_rate: " << this-> crossover_rate() << " + "; name << "crossover_selector: " << this-> crossover_selector().className() << " [" << this->rank( crossover_selectors) << "] + "; name << "aftercross_selector: " << this->aftercross_selector().className() << " [" << this->rank(aftercross_selectors) << "] + "; name << "crossover: " << this-> crossover().className() << " [" << this->rank( crossovers) << "] + "; name << "mutation_rate: " << this-> mutation_rate() << " + "; name << "mutation_selector: " << this-> mutation_selector().className() << " [" << this->rank( mutation_selectors) << "] + "; name << "mutation: " << this-> mutation().className() << " [" << this->rank( mutations) << "] + "; name << "replacement: " << this-> replacement().className() << " [" << this->rank( replacements) << "] + "; name << "continuator: " << this-> continuator().className() << " [" << this->rank( continuators) << "] + "; name << "offspring_size: " << this-> offspring_size() << ""; return name.str(); } protected: eoEvalFunc& _eval; eoInit& _init; const size_t _max_evals; const size_t _max_restarts; public: /** Currently selected continuator. */ eoContinue& continuator() { const size_t r = this->rank(continuators); assert(r < continuators.size()); return continuators.instantiate(r); } /** Currently selected crossover_rate. */ double& crossover_rate() { // We could have used `decltype(crossover_rates)::Type` instead of `double`, here, // but this is less readable and the type is declared just above, // so we are supposed to know it. const double val = this->value(crossover_rates); assert(crossover_rates.min() <= val and val <= crossover_rates.max()); return crossover_rates.instantiate(val); } /** Currently selected crossover. */ eoQuadOp& crossover() { const size_t r = this->rank(crossovers); assert(r < crossovers.size()); return crossovers.instantiate(r); } /** Currently selected mutation_rate. */ double& mutation_rate() { const double val = this->value(mutation_rates); assert(mutation_rates.min() <= val and val <= mutation_rates.max()); return mutation_rates.instantiate(val); } /** Currently selected mutation. */ eoMonOp& mutation() { const size_t r = this->rank(mutations); assert(r < mutations.size()); return mutations.instantiate(r); } /** Currently selected crossover_selector. */ eoSelectOne& crossover_selector() { const size_t r = this->rank(crossover_selectors); assert(r < crossover_selectors.size()); return crossover_selectors.instantiate(r); } /** Currently selected aftercross_selector. */ eoSelectOne& aftercross_selector() { const size_t r = this->rank(aftercross_selectors); assert(r < aftercross_selectors.size()); return aftercross_selectors.instantiate(r); } /** Currently selected mutation_selector. */ eoSelectOne& mutation_selector() { const size_t r = this->rank(mutation_selectors); assert(r < mutation_selectors.size()); return mutation_selectors.instantiate(r); } /** Currently selected offspring_size. */ size_t& offspring_size() { const size_t val = this->len(offspring_sizes); assert(offspring_sizes.min() <= val and val <= offspring_sizes.max()); return offspring_sizes.instantiate(val); } /** Currently selected replacement. */ eoReplacement& replacement() { const size_t r = this->rank(replacements); assert(r < replacements.size()); return replacements.instantiate(r); } }; #endif // _eoAlgoFoundryFastGA_H_