From cd7787bbab55983fd05d970af2a3aea1f2dcea10 Mon Sep 17 00:00:00 2001 From: nojhan Date: Wed, 7 Jul 2021 21:00:13 +0200 Subject: [PATCH] feat: adds eoForgeScalar and eoParameterFoundry - Get arithmetic types out of eoOperatorFoundry in eoParameterFoundry. - Allow for metric space variables management within irace. --- eo/contrib/irace/fastga.cpp | 155 +++++++++++++++++++++++------------ eo/src/eoAlgoFoundry.h | 19 +++++ eo/src/eoAlgoFoundryFastGA.h | 18 ++-- eo/src/eoForge.h | 83 +++++++++++++++++-- 4 files changed, 202 insertions(+), 73 deletions(-) diff --git a/eo/contrib/irace/fastga.cpp b/eo/contrib/irace/fastga.cpp index 3334974f5..9c8c94088 100644 --- a/eo/contrib/irace/fastga.cpp +++ b/eo/contrib/irace/fastga.cpp @@ -41,17 +41,17 @@ eoAlgoFoundryFastGA& make_foundry( // foundry.continuators.add< eoSteadyFitContinue >(10,i); // } - for(double i=0.0; i<1.0; i+=0.2) { - foundry.crossover_rates.add(i); - foundry.mutation_rates.add(i); - } + // for(double i=0.0; i<1.0; i+=0.2) { + // foundry.crossover_rates.add(i); + // foundry.mutation_rates.add(i); + // } /***** Offsprings size *****/ // for(size_t i=5; i<100; i+=10) { // foundry.offspring_sizes.add(i); // } - foundry.offspring_sizes.add(0); // 0 = use parents fixed pop size. + foundry.offspring_sizes.setup(0,100); // 0 = use parents fixed pop size. /***** Crossovers ****/ for(double i=0.1; i<1.0; i+=0.2) { @@ -61,7 +61,7 @@ eoAlgoFoundryFastGA& make_foundry( foundry.crossovers.add< eoNPtsBitXover >(i); // nb of points } - foundry.crossovers.add< eo1PtBitXover >(); + // foundry.crossovers.add< eo1PtBitXover >(); // Same as NPts=1 /***** Mutations ****/ double p = 1.0; // Probability of flipping each bit. @@ -119,10 +119,8 @@ eoAlgoFoundryFastGA& make_foundry( Bits::Fitness fake_func(const Bits&) { return 0; } -void print_irace_full(const eoParam& param, const size_t slot_size, std::string type="i", std::ostream& out = std::cout) +void print_irace_categorical(const eoParam& param, const size_t slot_size, std::string type="c", std::ostream& out = std::cout) { - assert(type == "i" or type =="c"); - // If there is no choice to be made on this operator, comment it out. if(slot_size - 1 <= 0) { out << "# "; @@ -136,41 +134,77 @@ void print_irace_full(const eoParam& param, const size_t slot_size, std::string << "\t\"--" << param.longName() << "=\"" << "\t" << type; - if(type == "i") { - if(slot_size -1 <= 0) { - out << "\t(0)"; - } else { - out << "\t(0," << slot_size-1 << ")"; - } - out << std::endl; - } else if(type == "c") { - out << "\t(0"; - for(size_t i=1; i +void print_irace_ranged(const eoParam& param, const T min, const T max, std::string type="r", std::ostream& out = std::cout) +{ + // If there is no choice to be made on this operator, comment it out. + if(max - min <= 0) { + out << "# "; + } + + // irace doesn't support "-" in names. + std::string irace_name = param.longName(); + irace_name.erase(std::remove(irace_name.begin(), irace_name.end(), '-'), irace_name.end()); + + out << irace_name + << "\t\"--" << param.longName() << "=\"" + << "\t" << type; + + if(max-min <= 0) { + out << "\t(?)"; + } else { + out << "\t(" << min << "," << max << ")"; + } + out << std::endl; +} + + template -void print_irace_typed(const eoParam& param, const eoForgeVector& op_foundry, std::ostream& out = std::cout) +void print_irace_oper(const eoParam& param, const eoOperatorFoundry& op_foundry, std::ostream& out = std::cout) { - print_irace_full(param, op_foundry.size(), "c", out); + print_irace_categorical(param, op_foundry.size(), "c", out); } -template<> -void print_irace_typed(const eoParam& param, const eoForgeVector& op_foundry, std::ostream& out) +// FIXME generalize to any scalar type with enable_if +// template +void print_irace_param( + const eoParam& param, + // const eoParameterFoundry::value >::type>& op_foundry, + const eoParameterFoundry& op_foundry, + std::ostream& out) { - print_irace_full(param, op_foundry.size(), "i", out); + print_irace_ranged(param, op_foundry.min(), op_foundry.max(), "r", out); } -template -void print_irace(const eoParam& param, const OPF& op_foundry, std::ostream& out = std::cout) +// template +void print_irace_param( + const eoParam& param, + // const eoParameterFoundry::value >::type>& op_foundry, + const eoParameterFoundry& op_foundry, + std::ostream& out) { - print_irace_typed(param, op_foundry, out); + print_irace_ranged(param, op_foundry.min(), op_foundry.max(), "i", out); } +template +void print_irace(const eoParam& param, const eoOperatorFoundry& op_foundry, std::ostream& out = std::cout) +{ + print_irace_oper(param, op_foundry, out); +} +template +void print_irace(const eoParam& param, const eoParameterFoundry& op_foundry, std::ostream& out = std::cout) +{ + print_irace_param/**/(param, op_foundry, out); +} + void print_operator_typed(const eoFunctorBase& op, std::ostream& out) { @@ -182,8 +216,8 @@ void print_operator_typed(const double& op, std::ostream& out) out << op; } -template -void print_operators(const eoParam& param, OPF& op_foundry, std::ostream& out = std::cout, std::string indent=" ") +template +void print_operators(const eoParam& param, eoOperatorFoundry& op_foundry, std::ostream& out = std::cout, std::string indent=" ") { out << indent << op_foundry.size() << " " << param.longName() << ":" << std::endl; for(size_t i=0; i < op_foundry.size(); ++i) { @@ -194,6 +228,11 @@ void print_operators(const eoParam& param, OPF& op_foundry, std::ostream& out = } } +template +void print_operators(const eoParam& param, eoParameterFoundry& op_foundry, std::ostream& out = std::cout, std::string indent=" ") +{ + out << indent << "[" << op_foundry.min() << "," << op_foundry.max() << "] " << param.longName() << "." << std::endl; +} // Problem configuration. struct Problem { @@ -322,17 +361,23 @@ int main(int argc, char* argv[]) // const size_t generations = std::numeric_limits::max(); eo::log << eo::debug << "Number of generations: " << generations << std::endl; - /***** operators / parameters *****/ + /***** metric parameters *****/ + auto crossover_rate_p = parser.getORcreateParam(0.5, + "crossover-rate", "", + 'C', "Operator Choice", /*required=*/true); + const double crossover_rate = crossover_rate_p.value(); + + auto mutation_rate_p = parser.getORcreateParam(0, + "mutation-rate", "", + 'M', "Operator Choice", /*required=*/true); + const double mutation_rate = mutation_rate_p.value(); + + /***** operators *****/ auto continuator_p = parser.getORcreateParam(0, "continuator", "Stopping criterion", 'o', "Operator Choice", /*required=*/false); // Single alternative, not required. const size_t continuator = continuator_p.value(); - auto crossover_rate_p = parser.getORcreateParam(0, - "crossover-rate", "", - 'C', "Operator Choice", /*required=*/true); - const size_t crossover_rate = crossover_rate_p.value(); - auto crossover_selector_p = parser.getORcreateParam(0, "cross-selector", "How to selects candidates for cross-over", 's', "Operator Choice", /*required=*/true); @@ -348,11 +393,6 @@ int main(int argc, char* argv[]) 'a', "Operator Choice", /*required=*/false); // Single alternative, not required. const size_t aftercross_selector = aftercross_selector_p.value(); - auto mutation_rate_p = parser.getORcreateParam(0, - "mutation-rate", "", - 'M', "Operator Choice", /*required=*/true); - const size_t mutation_rate = mutation_rate_p.value(); - auto mutation_selector_p = parser.getORcreateParam(0, "mut-selector", "How to selects candidate for mutation", 'u', "Operator Choice", /*required=*/true); @@ -393,19 +433,21 @@ int main(int argc, char* argv[]) print_operators( replacement_p, fake_foundry.replacements , std::clog); print_operators( offspring_size_p, fake_foundry.offspring_sizes , std::clog); + size_t fake_sample_size = 10; size_t n = - fake_foundry.crossover_rates.size() + fake_sample_size //crossover_rates * fake_foundry.crossover_selectors.size() * fake_foundry.crossovers.size() * fake_foundry.aftercross_selectors.size() - * fake_foundry.mutation_rates.size() + * fake_sample_size //mutation_rates * fake_foundry.mutation_selectors.size() * fake_foundry.mutations.size() * fake_foundry.replacements.size() * fake_foundry.continuators.size() - * fake_foundry.offspring_sizes.size(); + * fake_sample_size //offspring_sizes + ; std::clog << std::endl; - std::clog << n << " possible algorithms configurations." << std::endl; + std::clog << "~" << n << " possible algorithms configurations." << std::endl; std::clog << "Ranges of configurable parameters (redirect the stdout in a file to use it with iRace): " << std::endl; @@ -445,16 +487,22 @@ int main(int argc, char* argv[]) // Build up an algorithm name from main parameters. std::ostringstream name; name << "FastGA"; - for(auto& p : {pop_size_p, - crossover_rate_p, + for(auto& p : { crossover_selector_p, crossover_p, aftercross_selector_p, - mutation_rate_p, mutation_selector_p, mutation_p, - replacement_p, - offspring_size_p}) { + replacement_p }) { + name << "_" << p.shortName() << "=" << p.getValue(); + } + for(auto& p : { + crossover_rate_p, + mutation_rate_p }) { + name << "_" << p.shortName() << "=" << p.getValue(); + } + for(auto& p : {pop_size_p, + offspring_size_p }) { name << "_" << p.shortName() << "=" << p.getValue(); } std::clog << name.str() << std::endl; @@ -580,7 +628,8 @@ int main(int argc, char* argv[]) std::clog << "Attainment matrix distribution: " << std::endl; assert(mat.size() > 0); assert(mat[0].size() > 1); - for(size_t i = mat.size()-1; i >= 0; --i) { + for(size_t i = mat.size()-1; i > 0; --i) { + assert(mat[i].size() >= 1); std::cout << mat[i][0]; for(size_t j = 1; j < mat[i].size(); ++j) { std::cout << "," << mat[i][j]; diff --git a/eo/src/eoAlgoFoundry.h b/eo/src/eoAlgoFoundry.h index 19ffb5023..2e7064c99 100644 --- a/eo/src/eoAlgoFoundry.h +++ b/eo/src/eoAlgoFoundry.h @@ -59,9 +59,28 @@ class eoOperatorFoundry : public eoForgeVector< Itf > size_t index() const { return _index; } protected: + //! Unique index in the eoAlgoFoundry. size_t _index; }; +template +class eoParameterFoundry : public eoForgeScalar< Itf > +{ + static_assert(std::is_arithmetic::value, + "eoParameterFoundry should only be used on arithmetic types (i.e. integer or floating point types)"); + + public: + eoParameterFoundry(size_t encoding_index, Itf min, Itf max) : + eoForgeScalar(min, max), + _index(encoding_index) + { } + + size_t index() const { return _index; } + + protected: + //! Unique index in the eoAlgoFoundry. + size_t _index; +}; /** Interface of a Foundry: a class that instantiate an eoAlgo on-the-fly, given a choice of its operators. * diff --git a/eo/src/eoAlgoFoundryFastGA.h b/eo/src/eoAlgoFoundryFastGA.h index ed766ff88..59f7dcfb2 100644 --- a/eo/src/eoAlgoFoundryFastGA.h +++ b/eo/src/eoAlgoFoundryFastGA.h @@ -90,18 +90,18 @@ class eoAlgoFoundryFastGA : public eoAlgoFoundry ) : eoAlgoFoundry(10), - crossover_rates(0, false), + crossover_rates(0, 0.0, 1.0), crossover_selectors(1, false), crossovers(2, false), aftercross_selectors(3, false), - mutation_rates(4, 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, false), + offspring_sizes(9, 0, std::numeric_limits::max()), _eval(eval), _init(init), _max_evals(max_evals), @@ -111,34 +111,31 @@ class eoAlgoFoundryFastGA : public eoAlgoFoundry public: /* Operators containers @{ */ - eoOperatorFoundry< double > crossover_rates; + eoParameterFoundry< double > crossover_rates; eoOperatorFoundry< eoSelectOne > crossover_selectors; eoOperatorFoundry< eoQuadOp > crossovers; eoOperatorFoundry< eoSelectOne > aftercross_selectors; - eoOperatorFoundry< double > mutation_rates; + eoParameterFoundry< double > mutation_rates; eoOperatorFoundry< eoSelectOne > mutation_selectors; eoOperatorFoundry< eoMonOp > mutations; eoOperatorFoundry< eoReplacement > replacements; eoOperatorFoundry< eoContinue > continuators; - eoOperatorFoundry< size_t > offspring_sizes; + eoParameterFoundry< size_t > offspring_sizes; /* @} */ /** instantiate and call the pre-selected algorithm. */ void operator()(eoPop& pop) { - assert( crossover_rates.size() > 0); assert(this->at( crossover_rates.index()) < crossover_rates.size()); assert( crossover_selectors.size() > 0); assert(this->at( crossover_selectors.index()) < crossover_selectors.size()); assert( crossovers.size() > 0); assert(this->at( crossovers.index()) < crossovers.size()); assert(aftercross_selectors.size() > 0); assert(this->at(aftercross_selectors.index()) < aftercross_selectors.size()); - assert( mutation_rates.size() > 0); assert(this->at( mutation_rates.index()) < mutation_rates.size()); assert( mutation_selectors.size() > 0); assert(this->at( mutation_selectors.index()) < mutation_selectors.size()); assert( mutations.size() > 0); assert(this->at( mutations.index()) < mutations.size()); assert( replacements.size() > 0); assert(this->at( replacements.index()) < replacements.size()); assert( continuators.size() > 0); assert(this->at( continuators.index()) < continuators.size()); - assert( offspring_sizes.size() > 0); assert(this->at( offspring_sizes.index()) < offspring_sizes.size()); // Objective function calls counter eoEvalCounterThrowException eval(_eval, _max_evals); @@ -212,7 +209,6 @@ class eoAlgoFoundryFastGA : public eoAlgoFoundry double& crossover_rate() { - assert(this->at(crossover_rates.index()) < crossover_rates.size()); return crossover_rates.instantiate(this->at(crossover_rates.index())); } @@ -224,7 +220,6 @@ class eoAlgoFoundryFastGA : public eoAlgoFoundry double& mutation_rate() { - assert(this->at(mutation_rates.index()) < mutation_rates.size()); return mutation_rates.instantiate(this->at(mutation_rates.index())); } @@ -254,7 +249,6 @@ class eoAlgoFoundryFastGA : public eoAlgoFoundry size_t& offspring_size() { - assert(this->at(offspring_sizes.index()) < offspring_sizes.size()); return offspring_sizes.instantiate(this->at(offspring_sizes.index())); } diff --git a/eo/src/eoForge.h b/eo/src/eoForge.h index d1cae4c93..f36476579 100644 --- a/eo/src/eoForge.h +++ b/eo/src/eoForge.h @@ -215,7 +215,7 @@ class eoForgeOperator : public eoForgeInterface * * @ingroup Foundry */ -template +template class eoForgeVector : public std::vector*> { public: @@ -238,6 +238,19 @@ class eoForgeVector : public std::vector*> _no_cache(always_reinstantiate) { } + /** instantiate the operator managed at the given index. + */ + Itf& instantiate(double index) + { + double frac_part, int_part; + frac_part = std::modf(index, &int_part); + if(frac_part != 0) { + eo::log << eo::errors << "there is a fractional part in the given index (" << index << ")" << std::endl; + assert(frac_part != 0); + } + return this->at(static_cast(index))->instantiate(_no_cache); + } + /** Add an operator to the list. * * @warning When passing a reference (as it is often the case within ParadisEO), @@ -295,13 +308,6 @@ class eoForgeVector : public std::vector*> this->at(index) = pfo; } - /** instantiate the operator managed at the given index. - */ - Itf& instantiate(size_t index) - { - return this->at(index)->instantiate(_no_cache); - } - virtual ~eoForgeVector() { for(auto p : *this) { @@ -313,5 +319,66 @@ class eoForgeVector : public std::vector*> bool _no_cache; }; +template +class eoForgeScalar +{ + public: + using Interface = Itf; + + eoForgeScalar(Itf min, Itf max) : + _min(min), + _max(max) + { } + + /** Just return the same value, without managing any instantiation. + */ + Itf& instantiate(double value) + { + this->_value = value; + if(not (_min <= value and value <= _max) ) { + eo::log << eo::errors << "ERROR: the given value is out of range, I'll cap it." << std::endl; + assert(_min <= value and value <= _max); + if(value < _min) { + this->_value = _min; + return this->_value; + } + if(value > _max) { + this->_value = _max; + return this->_value; + } + } + return this->_value; + } + + Itf min() const { return _min; } + Itf max() const { return _max; } + + void min(Itf min) + { + assert(_min <= _max); + _min = min; + } + + void max(Itf max) + { + assert(_max >= _min); + _max = max; + } + + void setup(Itf min, Itf max) + { + _min = min; + _max = max; + assert(_min <= _max); + } + + // Nothing else, as it would not make sense. + protected: + Itf _value; + + Itf _min; + Itf _max; +}; + #endif // _eoForge_H_