From e5b5e8807d4e2f90880fcb334c9d67dfa663f03d Mon Sep 17 00:00:00 2001 From: Potalas Date: Wed, 5 Jan 2022 10:37:21 +0100 Subject: [PATCH] The new feature onlymutga with new mutation and experiments --- eo/contrib/irace/CMakeLists.txt | 4 + .../expe/gamma/irace-config/target-runner.py | 119 +++ eo/contrib/irace/expe/gamma/run_irace.ipynb | 267 ++++++ eo/contrib/irace/expe/gamma/run_irace.py | 12 + eo/contrib/irace/expe/gamma/target-runner.py | 119 +++ eo/contrib/irace/onlymutga.cpp | 770 ++++++++++++++++++ eo/src/ga/eoStandardBitMutation.h | 48 ++ 7 files changed, 1339 insertions(+) create mode 100755 eo/contrib/irace/expe/gamma/irace-config/target-runner.py create mode 100644 eo/contrib/irace/expe/gamma/run_irace.ipynb create mode 100644 eo/contrib/irace/expe/gamma/run_irace.py create mode 100644 eo/contrib/irace/expe/gamma/target-runner.py create mode 100644 eo/contrib/irace/onlymutga.cpp diff --git a/eo/contrib/irace/CMakeLists.txt b/eo/contrib/irace/CMakeLists.txt index 1e968d817..1c713bf5b 100644 --- a/eo/contrib/irace/CMakeLists.txt +++ b/eo/contrib/irace/CMakeLists.txt @@ -73,3 +73,7 @@ add_executable(fastga fastga.cpp) # target_link_libraries(fastga ${PARADISEO_LIBRARIES} ${IOH_LIBRARY} stdc++fs) target_link_libraries(fastga ${PARADISEO_LIBRARIES} fmt) +add_executable(onlymutga onlymutga.cpp) +# target_link_libraries(onlymutga ${PARADISEO_LIBRARIES} ${IOH_LIBRARY} stdc++fs) +target_link_libraries(onlymutga ${PARADISEO_LIBRARIES} fmt) + diff --git a/eo/contrib/irace/expe/gamma/irace-config/target-runner.py b/eo/contrib/irace/expe/gamma/irace-config/target-runner.py new file mode 100755 index 000000000..91db92b71 --- /dev/null +++ b/eo/contrib/irace/expe/gamma/irace-config/target-runner.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +############################################################################### +# This script is the command that is executed every run. +# Check the examples in examples/ +# +# This script is run in the execution directory (execDir, --exec-dir). +# +# PARAMETERS: +# argv[1] is the candidate configuration ID +# argv[2] is the instance ID +# argv[3] is the seed +# argv[4] is the instance name +# The rest (argv[5:]) are parameters to the run +# +# RETURN VALUE: +# This script should print one numerical value: the cost that must be minimized. +# Exit with 0 if no error, with 1 in case of error +############################################################################### + +import datetime +import os.path +import re +import subprocess +import sys + +exe = "../../../../release/onlymutga" + +problem = 19 +pop_size = 1 +offspring_size = 100 + +fixed_parameters = ["--problem", str(problem), "--crossover-rate", "0", "--mutation-rate", "1", "--pop-size", str(pop_size), " --offspring-size", str(offspring_size)] + +if __name__=='__main__': + if len(sys.argv) < 5: + print("\nUsage: ./target-runner.py \n") + sys.exit(1) + +# Get the parameters as command line arguments. +configuration_id = sys.argv[1] +instance_id = sys.argv[2] +seed = sys.argv[3] +instance = sys.argv[4] +slices_prop = sys.argv[5:] +#print(sys.argv) + +exe = os.path.expanduser(exe) + +cmd = [exe] + fixed_parameters + ["--instance", instance, "--seed", seed] + +residual_prob = 1 +cl_probs = [] +residual_size = 1 +cl_sizes = [] + +values = "" +sizes = "" +for i in range(len(slices_prop)): + cl_probs.append(residual_prob * float(slices_prop[i])) + cl_sizes.append(residual_size * (1-float(slices_prop[i]))) + residual_prob -= cl_probs[-1] + residual_size -= cl_sizes[-1] + values += "%.2f,"%cl_probs[-1] + sizes += "%.2f,"%cl_sizes[-1] + +cl_probs.append(residual_prob) +values += "%.2f"%cl_probs[-1] +sizes += "%.2f"%cl_sizes[-1] + +cmd += ["--cl-probs", values, "--cl-sizes", sizes] + + +# Define the stdout and stderr files. +out_file = "c" + str(configuration_id) + "-" + str(instance_id) + str(seed) + ".stdout" +err_file = "c" + str(configuration_id) + "-" + str(instance_id) + str(seed) + ".stderr" + +def target_runner_error(msg): + now = datetime.datetime.now() + print(str(now) + " error: " + msg) + sys.exit(1) + +def check_executable(fpath): + fpath = os.path.expanduser(fpath) + if not os.path.isfile(fpath): + target_runner_error(str(fpath) + " not found") + if not os.access(fpath, os.X_OK): + target_runner_error(str(fpath) + " is not executable") + +# This is an example of reading a number from the output. +def parse_output(out): + match = re.search(r'Best ([-+0-9.eE]+)', out.strip()) + if match: + return match.group(1); + else: + return "No match" + +check_executable (exe) + +outf = open(out_file, "w") +errf = open(err_file, "w") +return_code = subprocess.call(cmd, stdout = outf, stderr = errf) + +outf.close() +errf.close() + +if return_code != 0: + target_runner_error("command returned code " + str(return_code)) + +if not os.path.isfile(out_file): + target_runner_error("output file " + out_file + " not found.") + +cost = parse_output (open(out_file).read()) +#print(cost) +print(open(out_file).read().strip()) + +os.remove(out_file) +os.remove(err_file) +sys.exit(0) + diff --git a/eo/contrib/irace/expe/gamma/run_irace.ipynb b/eo/contrib/irace/expe/gamma/run_irace.ipynb new file mode 100644 index 000000000..251422d22 --- /dev/null +++ b/eo/contrib/irace/expe/gamma/run_irace.ipynb @@ -0,0 +1,267 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "04867792", + "metadata": {}, + "source": [ + "# Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "435212a6", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import seaborn as sb\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.ticker import MaxNLocator\n", + "import matplotlib.animation\n", + "from math import sqrt, log, cos, sin, pi\n", + "import numpy as np\n", + "import os\n", + "import shutil\n", + "import re\n", + "from subprocess import call\n", + "sb.set_style(\"whitegrid\")\n", + "#sb.set_palette(\"cubehelix\")\n", + "sb.set_palette(\"husl\")\n", + "sb.set(font_scale=1) # crazy big\n", + "sb.set_style('whitegrid', {'legend.frameon':True})\n", + "myfontsize = 12\n", + "titlesize = 15\n", + "%matplotlib notebook" + ] + }, + { + "cell_type": "markdown", + "id": "d76bdf6b", + "metadata": {}, + "source": [ + "# Function to generate the scenario file for irace" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d86a9ca8", + "metadata": {}, + "outputs": [], + "source": [ + "def scenario(filename=\"scenario.txt\", \n", + " parameterFile=\"parameters.txt\", \n", + " execDir=\".\", \n", + " logFile=\"./irace.Rdata\", \n", + " targetRunner = \"target-runner.py\", \n", + " maxExperiments = 100000,\n", + " digits = 2):\n", + " f = open(filename, \"w\")\n", + " f.write(\"parameterFile=\" + parameterFile +\"\\n\")\n", + " f.write(\"execDir=\" + execDir + \"\\n\")\n", + " f.write(\"logFile=\" + logFile + \"\\n\")\n", + " f.write(\"targetRunner=\" + targetRunner + \"\\n\")\n", + " f.write(\"maxExperiments=\" + maxExperiments + \"\\n\")\n", + " f.write(\"digits=\" + digits + \"\\n\")\n", + " f.close()" + ] + }, + { + "cell_type": "markdown", + "id": "1213321a", + "metadata": {}, + "source": [ + "# Function to generate the parameter file for irace" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "70d221c9", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate the param file for irace with all configuarable parameters\n", + "def parameters(filename=\"parameters.txt\"):\n", + " f = open(\"parameters.txt\", \"w\")\n", + " f.write(\"# name\\tswitch\\ttype\\tvalues\\n\") # head of the param file\n", + " cl_nb_part = 10 # number of category for the custom categorial probabilistic law\n", + " for i in range(cl_nb_part-1): # minus 1 slice than the number of categories\n", + " f.write(\"slice_prob_%s\\t\\\"\\\"\\tr\\t(0,1)\\n\"%i) # percentage of the residual probability for the slice\n", + "\n", + " ######################################### NOT USED YET ##########################################\n", + " #for i in range(cl_nb_part-1): # minus 1 slice than the number of categories\n", + " # f.write(\"slice_size_%s\\t\\\"\\\"\\tr\\t(0,1)\\n\"%i) # percentage of the residual size for the slice\n", + " #################################################################################################\n", + " f.close()" + ] + }, + { + "cell_type": "markdown", + "id": "65fcb69d", + "metadata": {}, + "source": [ + "# Fonction to generate problem dedicated target-runner.py" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "a18ba251", + "metadata": {}, + "outputs": [], + "source": [ + "def target_runner(origin=\"irace-config/target-runner.py\", path=\"target-runner.py\", problem=1):\n", + " \n", + " generalTR = open(origin, \"r\")\n", + " dedicatedTR = open(path, \"w\")\n", + " for line in generalTR:\n", + " if re.search(\"problem = \", line, flags=0):\n", + " dedicatedTR.write(\"problem = \" + str(problem) + \"\\n\")\n", + " else:\n", + " dedicatedTR.write(line)\n", + " generalTR.close()\n", + " dedicatedTR.close()" + ] + }, + { + "cell_type": "markdown", + "id": "59421dee", + "metadata": {}, + "source": [ + "# Run script" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "38dfec96", + "metadata": {}, + "outputs": [], + "source": [ + "results_directory = \"results\"\n", + "irace_path = \"/Library/Frameworks/R.framework/Versions/4.1-arm64/Resources/library/irace/bin/irace\"\n", + "instances_file = \"instances.txt\"\n", + "scenario_file = \"scenario.txt\"\n", + "parameters_file = \"parameters.txt\"\n", + "target_runner_file = \"target-runner.py\"\n", + "\n", + "# create or clear the results directory\n", + "if not os.path.isdir(results_directory):\n", + " os.mkdir(results_directory)\n", + " \n", + "for pb in range(1,3): # for each problem\n", + " # create or clear a subdirectory for the problem\n", + " problem_directory = results_directory + \"/problem_%s\"%pb\n", + " if os.path.isdir(problem_directory):\n", + " shutil.rmtree(problem_directory)\n", + " os.mkdir(problem_directory)\n", + " \n", + " # generate a custom target runner file for the problem\n", + " target_runner(path = problem_directory + \"/\" + target_runner_file, problem = pb)\n", + "\n", + " # copy the config files for iraces\n", + " for filename in [instances_file, scenario_file, parameters_file, target_runner_file]:\n", + " src = r'irace-config/' + filename\n", + " dst = problem_directory + \"/\" + filename\n", + " shutil.copyfile(src, dst)\n", + " \n", + " # run irace for\n", + " cmd = [irace_path, \"--scenario\", problem_directory + \"/\" + scenario_file] #, \"&> irace.log\"\n", + " call(cmd)\n", + "#call(cmd)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "b707ff3b", + "metadata": {}, + "outputs": [], + "source": [ + "shutil.rmtree(\"results\")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "460c588e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-10" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "call([\"../../release/onlymutga\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "eb234425", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'problem_1/default.instances'" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import shutil\n", + "import os\n", + "src = r'irace-config/default.instances'\n", + "dst = r'problem_1/default.instances'\n", + "shutil.copyfile(src, dst)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a62c411", + "metadata": {}, + "outputs": [], + "source": [ + "chmod u+x script.py\n", + "cp -a a b" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/eo/contrib/irace/expe/gamma/run_irace.py b/eo/contrib/irace/expe/gamma/run_irace.py new file mode 100644 index 000000000..caa7ea928 --- /dev/null +++ b/eo/contrib/irace/expe/gamma/run_irace.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Dec 14 12:16:17 2021 + +@author: labeiros +""" + +import sys + +print('Number of arguments:', len(sys.argv), 'arguments.') +print('Argument List:', str(sys.argv)) diff --git a/eo/contrib/irace/expe/gamma/target-runner.py b/eo/contrib/irace/expe/gamma/target-runner.py new file mode 100644 index 000000000..c1dca9c36 --- /dev/null +++ b/eo/contrib/irace/expe/gamma/target-runner.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +############################################################################### +# This script is the command that is executed every run. +# Check the examples in examples/ +# +# This script is run in the execution directory (execDir, --exec-dir). +# +# PARAMETERS: +# argv[1] is the candidate configuration ID +# argv[2] is the instance ID +# argv[3] is the seed +# argv[4] is the instance name +# The rest (argv[5:]) are parameters to the run +# +# RETURN VALUE: +# This script should print one numerical value: the cost that must be minimized. +# Exit with 0 if no error, with 1 in case of error +############################################################################### + +import datetime +import os.path +import re +import subprocess +import sys + +exe = "../../../../release/onlymutga" + +problem = blabla +pop_size = 1 +offspring_size = 100 + +fixed_parameters = ["--problem", str(problem), "--crossover-rate", "0", "--mutation-rate", "1", "--pop-size", str(pop_size), " --offspring-size", str(offspring_size)] + +if __name__=='__main__': + if len(sys.argv) < 5: + print("\nUsage: ./target-runner.py \n") + sys.exit(1) + +# Get the parameters as command line arguments. +configuration_id = sys.argv[1] +instance_id = sys.argv[2] +seed = sys.argv[3] +instance = sys.argv[4] +slices_prop = sys.argv[5:] +#print(sys.argv) + +exe = os.path.expanduser(exe) + +cmd = [exe] + fixed_parameters + ["--instance", instance, "--seed", seed] + +residual_prob = 1 +cl_probs = [] +residual_size = 1 +cl_sizes = [] + +values = "" +sizes = "" +for i in range(len(slices_prop)): + cl_probs.append(residual_prob * float(slices_prop[i])) + cl_sizes.append(residual_size * (1-float(slices_prop[i]))) + residual_prob -= cl_probs[-1] + residual_size -= cl_sizes[-1] + values += "%.2f,"%cl_probs[-1] + sizes += "%.2f,"%cl_sizes[-1] + +cl_probs.append(residual_prob) +values += "%.2f"%cl_probs[-1] +sizes += "%.2f"%cl_sizes[-1] + +cmd += ["--cl-probs", values, "--cl-sizes", sizes] + + +# Define the stdout and stderr files. +out_file = "c" + str(configuration_id) + "-" + str(instance_id) + str(seed) + ".stdout" +err_file = "c" + str(configuration_id) + "-" + str(instance_id) + str(seed) + ".stderr" + +def target_runner_error(msg): + now = datetime.datetime.now() + print(str(now) + " error: " + msg) + sys.exit(1) + +def check_executable(fpath): + fpath = os.path.expanduser(fpath) + if not os.path.isfile(fpath): + target_runner_error(str(fpath) + " not found") + if not os.access(fpath, os.X_OK): + target_runner_error(str(fpath) + " is not executable") + +# This is an example of reading a number from the output. +def parse_output(out): + match = re.search(r'Best ([-+0-9.eE]+)', out.strip()) + if match: + return match.group(1); + else: + return "No match" + +check_executable (exe) + +outf = open(out_file, "w") +errf = open(err_file, "w") +return_code = subprocess.call(cmd, stdout = outf, stderr = errf) + +outf.close() +errf.close() + +if return_code != 0: + target_runner_error("command returned code " + str(return_code)) + +if not os.path.isfile(out_file): + target_runner_error("output file " + out_file + " not found.") + +cost = parse_output (open(out_file).read()) +#print(cost) +print(open(out_file).read().strip()) + +os.remove(out_file) +os.remove(err_file) +sys.exit(0) + diff --git a/eo/contrib/irace/onlymutga.cpp b/eo/contrib/irace/onlymutga.cpp new file mode 100644 index 000000000..1c7b0937d --- /dev/null +++ b/eo/contrib/irace/onlymutga.cpp @@ -0,0 +1,770 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/***************************************************************************** + * ParadisEO algorithmic grammar definition. + *****************************************************************************/ + +// using Particle = eoRealParticle; +using Ints = eoInt, size_t>; +using Bits = eoBit, int>; + +// by enumerating candidate operators and their parameters. +eoAlgoFoundryFastGA& make_foundry( + eoFunctorStore& store, + eoInit& init, + eoEvalFunc& eval, + const size_t max_evals, + const size_t generations, + const double optimum, + const size_t pop_size, + const size_t offspring_size, + std::vector cl_sizes, + std::vector cl_values + ) +{ + // FIXME using max_restarts>1 does not allow to honor max evals. + auto& foundry = store.pack< eoAlgoFoundryFastGA >(init, eval, max_evals, /*max_restarts=*/1); + + /***** Continuators ****/ + auto& fitcont = store.pack< eoFitContinue >(optimum); + auto& gencont = store.pack< eoGenContinue >(generations); + auto combconts = std::make_shared< std::vector*> >(); + combconts->push_back( &fitcont ); + combconts->push_back( &gencont ); + foundry.continuators.add< eoCombinedContinue >( *combconts ); + // for(size_t i=1; i<10; i++) { + // foundry.continuators.add< eoGenContinue >(i); + // } + // for(size_t i=10; i < 100; i+=2 ) { + // 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); + // } + + /***** Offsprings size *****/ + // for(size_t i=5; i<100; i+=10) { + // foundry.offspring_sizes.add(i); + // } + + foundry.offspring_sizes.setup(0,offspring_size); // 0 = use parents fixed pop size. + + /***** Crossovers ****/ + for(double i=0.1; i<1.0; i+=0.2) { + foundry.crossovers.add< eoUBitXover >(i); // preference over 1 + } + for(size_t i=1; i < 10; i+=2) { + + foundry.crossovers.add< eoNPtsBitXover >(i); // nb of points + } + // foundry.crossovers.add< eo1PtBitXover >(); // Same as NPts=1 + + /***** Mutations ****/ + /* ######################## Removed by Alexis ######################## */ + /* + double p = 1.0; // Probability of flipping each bit. + // proba of flipping k bits, k drawn in uniform distrib + foundry.mutations.add< eoUniformBitMutation >(p); + // proba of flipping k bits, k drawn in binomial distrib + foundry.mutations.add< eoStandardBitMutation >(p); + // proba of flipping k bits, k drawn in binomial distrib, minus zero + foundry.mutations.add< eoConditionalBitMutation >(p); + // proba of flipping k bits, k drawn in binomial distrib, changing zeros to one + foundry.mutations.add< eoShiftedBitMutation >(p); + // proba of flipping k bits, k drawn in normal distrib + foundry.mutations.add< eoNormalBitMutation >(p); + // proba of flipping k bits, k drawn in powerlaw distrib + foundry.mutations.add< eoFastBitMutation >(p); + for(size_t i=1; i < 11; i+=2) { + // mutate k bits without duplicates + foundry.mutations.add< eoDetSingleBitFlip >(i); + } + */ + /* ######################## RbA END ######################## */ + + /* ######################## Add by Alexis ######################## */ + foundry.mutations.add< eoBucketBitMutation >(cl_sizes, cl_values); + /* ######################## AbA END ######################## */ + + /***** Selectors *****/ + for(eoOperatorFoundry>& ops : + {std::ref(foundry.crossover_selectors), + std::ref(foundry.mutation_selectors) }) { + + ops.add< eoRandomSelect >(); + ops.add< eoStochTournamentSelect >(0.5); + ops.add< eoSequentialSelect >(); + ops.add< eoProportionalSelect >(); + for(size_t i=2; i < 11; i+=4) { + ops.add< eoDetTournamentSelect >(i); + } + } + + foundry.aftercross_selectors.add< eoRandomSelect >(); + + + /***** Replacements ****/ + /* ######################## Removed by Alexis ######################## */ + /* + foundry.replacements.add< eoPlusReplacement >(); + foundry.replacements.add< eoCommaReplacement >(); + foundry.replacements.add< eoSSGAWorseReplacement >(); + for(double i=0.51; i<0.92; i+=0.2) { + foundry.replacements.add< eoSSGAStochTournamentReplacement >(i); + } + for(size_t i=2; i < 11; i+=2) { + foundry.replacements.add< eoSSGADetTournamentReplacement >(i); + } + */ + /* ######################## RbA END ######################## */ + + /* ######################## Add by Alexis ######################## */ + //foundry.replacements.add< eoSSGADetTournamentReplacement >(1); + foundry.replacements.add< eoCommaReplacement >(); + /* ######################## AbA END ######################## */ + + return foundry; +} + +/***************************************************************************** + * irace helper functions. + *****************************************************************************/ + +Bits::Fitness fake_func(const Bits&) { return 0; } + +void print_irace_categorical(const eoParam& param, const size_t slot_size, std::string type="c", std::ostream& out = std::cout) +{ + // If there is no choice to be made on this operator, comment it out. + if(slot_size - 1 <= 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; + + 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_oper(const eoParam& param, const eoOperatorFoundry& op_foundry, std::ostream& out = std::cout) +{ + print_irace_categorical(param, op_foundry.size(), "c", 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_ranged(param, op_foundry.min(), op_foundry.max(), "r", out); +} + +// template +void print_irace_param( + const eoParam& param, + // const eoParameterFoundry::value >::type>& op_foundry, + const eoParameterFoundry& op_foundry, + std::ostream& 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_irace(const eoParam& param, const size_t min, const size_t max, std::ostream& out = std::cout) +{ + print_irace_ranged(param, min, max, "i", out); +} + +void print_operator_typed(const eoFunctorBase& op, std::ostream& out) +{ + out << op.className(); +} + +void print_operator_typed(const double& op, std::ostream& out) +{ + out << op; +} + +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) { + out << indent << indent << i << ": "; + auto& op = op_foundry.instantiate(i); + print_operator_typed(op, out); + out << std::endl; + } +} + +template +void print_operators(const eoParam& param, T min, T max, std::ostream& out = std::cout, std::string indent=" ") +{ + out << indent << "[" << min << "," << max << "] " << param.longName() << "." << std::endl; +} + +template +void print_operators(const eoParam& param, eoParameterFoundry& op_foundry, std::ostream& out = std::cout, std::string indent=" ") +{ + print_operators(param, op_foundry.min(), op_foundry.max(), out, indent); +} + +// Problem configuration. +struct Problem { + double dummy; + size_t epistasis; + size_t neutrality; + size_t ruggedness; + size_t max_target; + size_t dimension; + friend std::ostream& operator<<(std::ostream& os, const Problem& pb); +}; + +std::ostream& operator<<(std::ostream& os, const Problem& pb) +{ + os << "u=" << pb.dummy << "_" + << "e=" << pb.epistasis << "_" + << "n=" << pb.neutrality << "_" + << "r=" << pb.ruggedness << "_" + << "t=" << pb.max_target << "_" + << "d=" << pb.dimension; + return os; +} + +/***************************************************************************** + * IOH problem adaptation. + *****************************************************************************/ + +class WModelFlat : public ioh::problem::wmodel::WModelOneMax +{ + public: + WModelFlat(const int instance, const int n_variables, + const double dummy_para, const int epistasis_para, const int neutrality_para, + const int ruggedness_para) + : WModelOneMax(instance, n_variables, dummy_para, epistasis_para, neutrality_para, ruggedness_para) + { } + + protected: + double transform_objectives(const double y) override + { // Disable objective function shift & scaling. + return y; + } +}; + +/***************************************************************************** + * Command line interface. + *****************************************************************************/ + +int main(int argc, char* argv[]) +{ + /***** Global parameters. *****/ + enum { NO_ERROR = 0, ERROR_USAGE = 100 }; + + std::map benchmark { + /* ┌ problem index in the map + * │ ┌ problem ID in IOH experimenter + * │ │ ┌ dummy + * │ │ │ ┌ epistasis + * │ │ │ │ ┌ neutrality + * │ │ │ │ │ ┌ ruggedness + * │ │ │ │ │ │ ┌ max target + * │ │ │ │ │ │ │ ┌ dimension (bitstring length) */ + { 0 /* 1*/, {0, 6, 2, 10, 10, 20 }}, + { 1 /* 2*/, {0, 6, 2, 18, 10, 20 }}, + { 2 /* 3*/, {0, 5, 1, 72, 16, 16 }}, + { 3 /* 4*/, {0, 9, 3, 72, 16, 48 }}, + { 4 /* 5*/, {0, 23, 1, 90, 25, 25 }}, + { 5 /* 6*/, {0, 2, 1, 397, 32, 32 }}, + { 6 /* 7*/, {0, 11, 4, 0, 32, 128 }}, + { 7 /* 8*/, {0, 14, 4, 0, 32, 128 }}, + { 8 /* 9*/, {0, 8, 4, 128, 32, 128 }}, + { 9 /*10*/, {0, 36, 1, 245, 50, 50 }}, + {10 /*11*/, {0, 21, 2, 256, 50, 100 }}, + {11 /*12*/, {0, 16, 3, 613, 50, 150 }}, + {12 /*13*/, {0, 32, 2, 256, 64, 128 }}, + {13 /*14*/, {0, 21, 3, 16, 64, 192 }}, + {14 /*15*/, {0, 21, 3, 256, 64, 192 }}, + {15 /*16*/, {0, 21, 3, 403, 64, 192 }}, + {16 /*17*/, {0, 52, 4, 2, 64, 256 }}, + {17 /*18*/, {0, 60, 1, 16, 75, 75 }}, + {18 /*19*/, {0, 32, 2, 4, 75, 150 }}, + {19 /*20?*/, {0, 0, 0, 0, 0, 64 }} // Add by Alexis + }; + + eoFunctorStore store; + + eoParser parser(argc, argv, "OnlymutGA interface for iRace"); + + /***** Problem parameters *****/ + auto problem_p = parser.getORcreateParam(0, + "problem", "Problem ID", + 'p', "Problem", /*required=*/true); + const size_t problem = problem_p.value(); + assert(0 <= problem and problem < benchmark.size()); + + // const size_t dimension = parser.getORcreateParam(1000, + // "dimension", "Dimension size", + // 'd', "Problem").value(); + const size_t dimension = benchmark[problem].dimension; + + auto instance_p = parser.getORcreateParam(0, + "instance", "Instance ID", + 'i', "Instance", /*required=*/false); + const size_t instance = instance_p.value(); + + const size_t max_evals = parser.getORcreateParam(5 * dimension, + "max-evals", "Maximum number of evaluations (default: 5*dim, else the given value)", + 'e', "Stopping criterion").value(); + + const size_t buckets = parser.getORcreateParam(100, + "buckets", "Number of buckets for discretizing the ECDF", + 'b', "Performance estimation").value(); + + /***** Generic options *****/ + uint32_t seed = + parser.getORcreateParam(0, + "seed", "Random number seed (0 = epoch)", + 'S').value(); + if(seed == 0) { + seed = time(0); + } + // rng is a global + rng.reseed(seed); + + bool full_log = + parser.getORcreateParam(0, + "full-log", "Log the full search in CSV files"/* (using the IOH profiler format)"*/, + 'F').value(); + + bool output_mat = + parser.getORcreateParam(0, + "output-mat", "Output the aggregated attainment matrix instead of its scalar sum (fancy colormap on stderr, parsable CSV on stdout).", + 'A').value(); + + /***** populations sizes *****/ + auto pop_size_p = parser.getORcreateParam(1, + "pop-size", "Population size", + 'P', "Operator Choice", /*required=*/false); // Changed by Alexis: 5 -> 1 + const size_t pop_size = pop_size_p.value(); + const size_t pop_size_max = 10; + + auto offspring_size_p = parser.getORcreateParam(10, + "offspring-size", "Offsprings size (0 = same size than the parents pop, see --pop-size)", + 'O', "Operator Choice", /*required=*/false); // Single alternative, not required. // Changed by Alexis: 0 -> 10 + const size_t offspring_size = offspring_size_p.value(); + + size_t generations = static_cast(std::floor( + static_cast(max_evals) / static_cast(pop_size))); + // const size_t generations = std::numeric_limits::max(); + if(generations < 1) { + generations = 1; + } + + /***** metric parameters *****/ + auto crossover_rate_p = parser.getORcreateParam(0, + "crossover-rate", "", + 'C', "Operator Choice", /*required=*/false); // Changed by Alexis: 0.5 -> 0 | true -> false + const double crossover_rate = crossover_rate_p.value(); + + auto mutation_rate_p = parser.getORcreateParam(1, + "mutation-rate", "", + 'M', "Operator Choice", /*required=*/false); // Changed by Alexis: 0 -> 1 | true -> false + 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_selector_p = parser.getORcreateParam(0, + "cross-selector", "How to selects candidates for cross-over", + 's', "Operator Choice", /*required=*/false); // Changed by Alexis: true -> false + const size_t crossover_selector = crossover_selector_p.value(); + + auto crossover_p = parser.getORcreateParam(0, + "crossover", "", + 'c', "Operator Choice", /*required=*/false); // Changed by Alexis: true -> false + const size_t crossover = crossover_p.value(); + + auto aftercross_selector_p = parser.getORcreateParam(0, + "aftercross-selector", "How to selects between the two individuals altered by cross-over which one will mutate", + 'a', "Operator Choice", /*required=*/false); // Single alternative, not required. + const size_t aftercross_selector = aftercross_selector_p.value(); + + auto mutation_selector_p = parser.getORcreateParam(0, + "mut-selector", "How to selects candidate for mutation", + 'u', "Operator Choice", /*required=*/false); // Changed by Alexis: true -> false + const size_t mutation_selector = mutation_selector_p.value(); + + auto mutation_p = parser.getORcreateParam(0, + "mutation", "", + 'm', "Operator Choice", /*required=*/false); // Changed by Alexis: true -> false + const size_t mutation = mutation_p.value(); + + auto replacement_p = parser.getORcreateParam(0, + "replacement", "", + 'r', "Operator Choice", /*required=*/false); // Changed by Alexis: true -> false + const size_t replacement = replacement_p.value(); + + /* ######################## Add by Alexis ######################## */ + + auto cl_values_p = parser.getORcreateParam("0.8,0.2", + "cl-probs", "Probabilities of each part for the custom law (sum = 1)", + 'y', "Operator Choice", false); + + std::string cl_v = cl_values_p.value(); + std::vector cl_probs = std::vector(); + + std::string sep = ","; + + size_t pos = 0; + std::string token; + while ((pos = cl_v.find(sep)) != std::string::npos) { + token = cl_v.substr(0, pos); + cl_probs.push_back(std::stod(token)); + cl_v.erase(0, pos + sep.length()); + } + cl_probs.push_back(std::stod(cl_v)); + + auto cl_sizes_p = parser.getORcreateParam("0.5,0.5", + "cl-sizes", "Proportion sizes of each part for the custom law (sum = 1)", + 'x', "Operator Choice", false); + std::string cl_s = cl_sizes_p.value(); + std::vector cl_sizes = std::vector(); + + pos = 0; + while ((pos = cl_s.find(sep)) != std::string::npos) { + token = cl_s.substr(0, pos); + cl_sizes.push_back(std::stod(token)); + cl_s.erase(0, pos + sep.length()); + } + cl_sizes.push_back(std::stod(cl_s)); + /* ######################## AbA END ######################## */ + + // Help + Verbose routines + make_verbose(parser); + make_help(parser, /*exit_after*/false, std::clog); + + if(parser.userNeedsHelp()) { + + // Fake operators, just to be able to call make_foundry + // to get the configured operators slots. + eoEvalFuncPtr fake_eval(fake_func); + eoUniformGenerator fake_gen(0, 1); + eoInitFixedLength fake_init(/*bitstring size=*/1, fake_gen); + auto fake_foundry = make_foundry(store, fake_init, fake_eval, max_evals, /*generations=*/ 1, 0, pop_size_max, offspring_size, cl_sizes, cl_probs); + + std::clog << std::endl << "Available operators:" << std::endl; + + print_operators( continuator_p, fake_foundry.continuators , std::clog); + print_operators( crossover_rate_p, fake_foundry.crossover_rates , std::clog); + print_operators( crossover_selector_p, fake_foundry.crossover_selectors , std::clog); + print_operators(aftercross_selector_p, fake_foundry.aftercross_selectors, std::clog); + print_operators( crossover_p, fake_foundry.crossovers , std::clog); + print_operators( mutation_rate_p, fake_foundry.mutation_rates , std::clog); + print_operators( mutation_selector_p, fake_foundry.mutation_selectors , std::clog); + print_operators( mutation_p, fake_foundry.mutations , std::clog); + print_operators( replacement_p, fake_foundry.replacements , std::clog); + print_operators( offspring_size_p, fake_foundry.offspring_sizes , std::clog); + print_operators( pop_size_p, (size_t)1, pop_size_max , std::clog); + /* ######################## Add by Alexis ######################## */ + print_operators( cl_values_p, "(1)", "(0.01,...,0.99)" , std::clog); + print_operators( cl_sizes_p, "(1)", "(0.01,...,0.99)" , std::clog); + /* ######################## AbA END ######################## */ + std::clog << std::endl; + + + + // If we were to make a DoE sampling numeric parameters, + // we would use that many samples: + size_t fake_sample_size = 10; + std::clog << "With " << fake_sample_size << " samples for numeric parameters..." << std::endl; + size_t n = + fake_sample_size //crossover_rates + * fake_foundry.crossover_selectors.size() + * fake_foundry.crossovers.size() + * fake_foundry.aftercross_selectors.size() + * fake_sample_size //mutation_rates + * fake_foundry.mutation_selectors.size() + * fake_foundry.mutations.size() + * fake_foundry.replacements.size() + * fake_foundry.continuators.size() + * fake_sample_size //offspring_sizes + * fake_sample_size //pop_size + ; + 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; + + // Do not print problem and instances, as they are managed separately by irace. + std::cout << "# name\tswitch\ttype\trange" << std::endl; + /* ######################## Removed by Alexis ######################## */ + /* + print_irace( continuator_p, fake_foundry.continuators , std::cout); + print_irace( crossover_rate_p, fake_foundry.crossover_rates , std::cout); + print_irace( crossover_selector_p, fake_foundry.crossover_selectors , std::cout); + print_irace(aftercross_selector_p, fake_foundry.aftercross_selectors, std::cout); + print_irace( crossover_p, fake_foundry.crossovers , std::cout); + print_irace( mutation_rate_p, fake_foundry.mutation_rates , std::cout); + print_irace( mutation_selector_p, fake_foundry.mutation_selectors , std::cout); + print_irace( mutation_p, fake_foundry.mutations , std::cout); + print_irace( replacement_p, fake_foundry.replacements , std::cout); + print_irace( offspring_size_p, fake_foundry.offspring_sizes , std::cout); + print_irace( pop_size_p, 1, pop_size_max , std::cout); + */ + /* ######################## RbA END ######################## */ + + //std::ofstream irace_param("fastga.params"); + //irace_param << "# name\tswitch\ttype\tvalues" << std::endl; + + exit(NO_ERROR); + } + + eo::log << eo::debug << "Maximum number of evaluations: " << max_evals << std::endl; + eo::log << eo::debug << "Number of generations: " << generations << std::endl; + + + /***************************************************************************** + * IOH stuff. + *****************************************************************************/ + + /***** IOH logger *****/ + auto max_target = benchmark[problem].max_target; + ioh::logger::eah::Log10Scale target_range(0, max_target, buckets); + ioh::logger::eah::Log10Scale budget_range(0, max_evals, buckets); + ioh::logger::EAH eah_logger(target_range, budget_range); + + ioh::logger::Combine loggers(eah_logger); + + std::shared_ptr csv_logger = nullptr; + if(full_log) { + // Build up an algorithm name from main parameters. + std::ostringstream name; + name << "OnlymutGA"; + for(auto& p : { + crossover_selector_p, + crossover_p, + aftercross_selector_p, + mutation_selector_p, + mutation_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; + + // Build up a problem description. + std::ostringstream desc; + desc << "pb=" << problem << "_"; + desc << benchmark[problem]; // Use the `operator<<` above. + std::clog << desc.str() << std::endl; + + std::filesystem::path folder = desc.str(); + std::filesystem::create_directories(folder); + + ioh::trigger::OnImprovement on_improvement; + ioh::watch::Evaluations evaluations; + ioh::watch::TransformedYBest transformed_y_best; + std::vector> t = {on_improvement}; + std::vector> w = {evaluations,transformed_y_best}; + csv_logger = std::make_shared( + // {std::ref(on_improvement)}, + // {std::ref(evaluations),std::ref(transformed_y_best)}, + t, w, + name.str(), + folder + ); + loggers.append(*csv_logger); + } + + /***** IOH problem *****/ + double w_dummy = benchmark[problem].dummy; + int w_epitasis = benchmark[problem].epistasis; + int w_neutrality = benchmark[problem].neutrality; + int w_ruggedness = benchmark[problem].ruggedness; + + // std::string problem_name = "OneMax"; + // problem_name = problem_name + // + "_D" + std::to_string((int)(w_dummy * dimension)) + // + "_E" + std::to_string(w_epitasis) + // + "_N" + std::to_string(w_neutrality) + // + "_R" + std::to_string(w_ruggedness); + + // ioh::problem::wmodel::WModelOneMax w_model_om( + WModelFlat w_model_om( + instance, + dimension, + w_dummy, + w_epitasis, + w_neutrality, + w_ruggedness); + + /***** Bindings *****/ + w_model_om.attach_logger(loggers); + + /***************************************************************************** + * Binding everything together. + *****************************************************************************/ + + eoEvalIOHproblem onemax_pb(w_model_om, loggers); + + // eoEvalPrint eval_print(onemax_pb, std::clog, "\n"); + // eoEvalFuncCounter eval_count(onemax_pb); + eoEvalCounterThrowException eval_count(onemax_pb, max_evals); + + eoPopLoopEval onemax_eval(eval_count); + + /***** Instanciate and run the algo *****/ + + eoBooleanGenerator bgen; + eoInitFixedLength onemax_init(/*bitstring size=*/dimension, bgen); + auto& foundry = make_foundry(store, onemax_init, eval_count, max_evals, generations, max_target, pop_size_max, offspring_size, cl_sizes, cl_probs); + + Ints encoded_algo(foundry.size()); + + encoded_algo[foundry.crossover_rates .index()] = crossover_rate; + encoded_algo[foundry.crossover_selectors .index()] = crossover_selector; + encoded_algo[foundry.crossovers .index()] = crossover; + encoded_algo[foundry.aftercross_selectors.index()] = aftercross_selector; + encoded_algo[foundry.mutation_rates .index()] = mutation_rate; + encoded_algo[foundry.mutation_selectors .index()] = mutation_selector; + encoded_algo[foundry.mutations .index()] = mutation; + encoded_algo[foundry.replacements .index()] = replacement; + encoded_algo[foundry.continuators .index()] = continuator; + encoded_algo[foundry.offspring_sizes .index()] = offspring_size; + + // std::clog << "Encoded algorithm:" << std::endl; + foundry.select(encoded_algo); + + std::clog << foundry.name() << std::endl; + + // // Evaluation of a forged encoded_algo on the sub-problem + // eoEvalFoundryFastGA eval_foundry( + // foundry, pop_size, + // onemax_init, onemax_eval, + // /*penalization=*/ dimension, // Worst case penalization. + // /*normalized=*/ false); // Use direct integer encoding. + // + // // Actually instanciate and run the algorithm. + // eval_foundry(encoded_algo); + + /***************************************************************************** + * Run and output results. + *****************************************************************************/ + + eoPop pop; + pop.append(pop_size, onemax_init); + try { + onemax_eval(pop,pop); + foundry(pop); // Actually run the selected algorithm. + + } catch(eoMaxEvalException e) { + eo::log << eo::debug << "Reached maximum evaluations: " << eval_count.getValue() << " / " << max_evals << std::endl; + } + + /***** IOH perf stats *****/ + double perf = ioh::logger::eah::stat::under_curve::volume(eah_logger); + + if(perf == 0 or perf > max_target * max_evals * 1.0) { + std::cerr << "WARNING: illogical performance? " << perf + << " Check the bounds or the algorithm." << std::endl; + } + + // std::clog << "After " << eval_count.getValue() << " / " << max_evals << " evaluations" << std::endl; + + if(output_mat) { + std::vector> mat = ioh::logger::eah::stat::distribution(eah_logger); + + // Fancy color map on clog. + std::clog << ioh::logger::eah::colormap(mat) << std::endl; + + // Parsable CSV on cout. + 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) { + 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]; + } + std::cout << std::endl; + } + + } else { + // iRace expects minimization + std::cout << -1 * perf << std::endl; + } +} diff --git a/eo/src/ga/eoStandardBitMutation.h b/eo/src/ga/eoStandardBitMutation.h index a2f7f12aa..fd6e4263e 100644 --- a/eo/src/ga/eoStandardBitMutation.h +++ b/eo/src/ga/eoStandardBitMutation.h @@ -222,4 +222,52 @@ class eoFastBitMutation : public eoStandardBitMutation double _beta; }; +/** Bucket mutation which assign probability for each bucket + * + * From: + * Carola Doerr, Johann Dréo, Alexis Robbes + */ +template +class eoBucketBitMutation : public eoStandardBitMutation +{ + public: + eoBucketBitMutation(std::vector bucketsSizes, std::vector bucketValues) : + _bucketsSizes(bucketsSizes), + _bucketValues(bucketValues) + + { + assert(bucketsSizes.size() != bucketValues.size()); + } + + virtual bool operator()(EOT& chrom) + { + + this->_nb = customlaw(chrom.size(), _bucketsSizes, _bucketValues); + // BitFlip operator is bound to the _nb reference, + // thus one don't need to re-instantiate. + return this->_bitflip(chrom); + } + + virtual std::string className() const {return "eoBucketBitMutation";} + + protected: + + double customlaw(unsigned n, std::vector bucketsSizes, std::vector bucketValues) + { + int targetBucketIndex = eo::rng.roulette_wheel(bucketValues); + int nb = 0; + int bucketIndex = 0; + while (nb < n && bucketIndex <= targetBucketIndex) + { + if (bucketIndex < targetBucketIndex) nb += int(n*bucketsSizes[bucketIndex]); + else nb += int(eo::rng.uniform(1, n*bucketsSizes[targetBucketIndex])); + } + if (nb > n) nb = n; + + return nb; + } + + std::vector _bucketsSizes; + std::vector _bucketValues; +}; #endif // _eoStandardBitMutation_h_