diff --git a/Bayesian-Optimization.zip b/Bayesian-Optimization.zip index bc80c02..72091ad 100644 Binary files a/Bayesian-Optimization.zip and b/Bayesian-Optimization.zip differ diff --git a/gen_config.py b/gen_config.py index 126b923..9d03d41 100644 --- a/gen_config.py +++ b/gen_config.py @@ -1,132 +1,160 @@ -import sys -import os -import json -import datetime -import subprocess - -class ExperimentEnvironment: - SLURM_SCRIPT_TEMPLATE = '''#!/bin/bash - -#SBATCH --job-name=##folder## -#SBATCH --array=0-##jobs_count## -#SBATCH --clusters=serial -#SBATCH --partition=serial_std -#SBATCH --mem=2048MB -#SBATCH --time=96:00:00 -#SBATCH --mail-user=your_email@domain.com -#SBATCH --mail-type=END,FAIL -#SBATCH --ntasks=1 -#SBATCH --cpus-per-task=4 -#SBATCH --output=##logs_out## -#SBATCH --error=##logs_err## - -num=##from_number## -FILE_ID=$((${SLURM_ARRAY_TASK_ID}+$num)) -python ../run_experiment.py configs/*${FILE_ID}.json -''' - - def __init__(self): - now = datetime.datetime.now() - suffix = now.strftime('%d-%m-%Y_%Hh%Mm%Ss') - folder_name = 'run_' + suffix - os.makedirs(folder_name, exist_ok=False) - print(f'Experiment root is: {folder_name}') - self.experiment_root = os.path.abspath(folder_name) - self.__max_array_size = 100 - self.__number_of_slurm_scripts = 0 - - def set_up_by_experiment_config_file(self, experiment_config_file_name): - self.__generate_configs(experiment_config_file_name) - self.__create_log_dir() - self.__generate_slurm_script() - - def __create_log_dir(self): - self.logs_folder = os.path.join(self.experiment_root, 'logs') - os.mkdir(self.logs_folder) - - def __generate_slurm_script(self): - self.__number_of_slurm_scripts = 0 - logs_out = os.path.join(self.logs_folder, '%A_%a.out') - logs_err = os.path.join(self.logs_folder, '%A_%a.err') - script = ExperimentEnvironment.SLURM_SCRIPT_TEMPLATE - script = script\ - .replace('##folder##', self.result_folder_prefix)\ - .replace('##logs_out##', logs_out)\ - .replace('##logs_err##', logs_err) - offset = 0 - for i in range(self.generated_configs // self.__max_array_size): - with open(os.path.join(self.experiment_root, f'slurm{self.__number_of_slurm_scripts}.sh'), 'w') as f: - f.write(script\ - .replace('##from_number##', str(offset))\ - .replace('##jobs_count##', str(self.__max_array_size - 1))) - offset += self.__max_array_size - self.__number_of_slurm_scripts += 1 - r = self.generated_configs % self.__max_array_size - if r > 0: - with open(os.path.join(self.experiment_root, f'slurm{self.__number_of_slurm_scripts}.sh'), 'w') as f: - f.write(script\ - .replace('##from_number##', str(offset))\ - .replace('##jobs_count##', str(r - 1))) - offset += r - self.__number_of_slurm_scripts += 1 - - def __generate_configs(self, experiment_config_file_name): - with open(experiment_config_file_name, 'r') as f: - config = json.load(f) - self.result_folder_prefix = config['folder'] - fids = config['fids'] - iids = config['iids'] - dims = config['dims'] - reps = config['reps'] - if 'extra' not in config.keys(): - config['extra'] = '' - optimizers = config['optimizers'] - lb, ub = config['lb'], config['ub'] - runs_number = len(optimizers) * len(fids) * len(iids) * len(dims) * reps - cur_config_number = 0 - configs_dir = os.path.join(self.experiment_root, 'configs') - os.makedirs(configs_dir, exist_ok=False) - with open(os.path.join(self.experiment_root, 'description.json'), 'w') as f: - json.dump(config, f, indent=4) - for my_optimizer_name in optimizers: - for fid in fids: - for iid in iids: - for dim in dims: - # print(f'Ids for opt={my_optimizer_name}, fid={fid}, iid={iid}, dim={dim} are [{cur_config_number}, {cur_config_number+reps-1}]') - for rep in range(reps): - experiment_config = { - 'folder': f'{self.result_folder_prefix}_Opt-{my_optimizer_name}_F-{fid}_Id-{iid}_Dim-{dim}_Rep-{rep}_NumExp-{cur_config_number}', - 'opt': my_optimizer_name, - 'fid': fid, - 'iid': iid, - 'dim': dim, - 'seed': rep, - 'lb': lb, - 'ub': ub, - } - cur_config_file_name = f'Opt-{my_optimizer_name}_F-{fid}_Id-{iid}_Dim-{dim}_Rep-{rep}_NumExp-{cur_config_number}.json' - with open(os.path.join(configs_dir, cur_config_file_name), 'w') as f: - json.dump(experiment_config, f) - cur_config_number += 1 - print(f'Generated {cur_config_number} files') - self.generated_configs = cur_config_number - def is_slurm_available(self): - try: - subprocess.run(["sbatch", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - return True - except FileNotFoundError: - return False - - def print_helper(self): - if self.is_slurm_available(): - print(f'cd {self.experiment_root} && for (( i=0; i<{self.__number_of_slurm_scripts}; ++i )); do sbatch slurm$i.sh; done') - - -def main(argv): - env = ExperimentEnvironment() - env.set_up_by_experiment_config_file(argv[1]) - env.print_helper() - - -if __name__ == '__main__': - main(sys.argv) +import sys +import os +import json +import datetime +import subprocess + +# #SBATCH --clusters=serial +class ExperimentEnvironment: + SLURM_SCRIPT_TEMPLATE = '''#!/bin/bash + +#SBATCH --job-name=##folder## +#SBATCH --array=0-##jobs_count## +#SBATCH --partition=gpu-medium +#SBATCH --mem=4096MB +#SBATCH --time=48:00:00 +#SBATCH --mail-user=olarterodriguezi@vuw.leidenuniv.nl +#SBATCH --mail-type=END,FAIL +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 +#SBATCH --output=##logs_out## +#SBATCH --error=##logs_err## + + +#// LOAD PYTHON +module load Python/3.10.4-GCCcore-11.3.0 + +#// ACTIVATE MODULE +source $HOME/BO_torch/bo-torch-run/bin/activate + +num=##from_number## +FILE_ID=$((${SLURM_ARRAY_TASK_ID}+$num)) + +# Define the directory containing the config files +CONFIG_DIR="##folder_name_configs##" + +# Find the file where NumExp-% matches TASK_ID +CONFIG_FILE=$(find "$CONFIG_DIR" -type f -name "*.json" | grep "NumExp-${FILE_ID}\.json") + +# Check if a config file was found +if [ -z "$CONFIG_FILE" ]; then + echo "Error: No matching config file found for NumExp-${FILE_ID}!" + exit 1 +fi + +echo $CONFIG_FILE + +python3 ../run_experiment.py $CONFIG_FILE +''' + + def __init__(self): + now = datetime.datetime.now() + suffix = now.strftime('%d-%m-%Y_%Hh%Mm%Ss') + folder_name = 'run_' + suffix + os.makedirs(folder_name, exist_ok=False) + print(f'Experiment root is: {folder_name}') + self.experiment_root = os.path.abspath(folder_name) + self.__max_array_size = 1000 + self.__number_of_slurm_scripts = 0 + + def set_up_by_experiment_config_file(self, experiment_config_file_name): + self.__generate_configs(experiment_config_file_name) + self.__create_log_dir() + self.__generate_slurm_script() + + def __create_log_dir(self): + self.logs_folder = os.path.join(self.experiment_root, 'logs') + os.mkdir(self.logs_folder) + + def __generate_slurm_script(self): + self.__number_of_slurm_scripts = 0 + logs_out = os.path.join(self.logs_folder, '%A_%a.out') + logs_err = os.path.join(self.logs_folder, '%A_%a.err') + script = ExperimentEnvironment.SLURM_SCRIPT_TEMPLATE + script = script\ + .replace('##folder##', self.result_folder_prefix)\ + .replace('##logs_out##', logs_out)\ + .replace('##logs_err##', logs_err)\ + .replace('##folder_name_configs##', + os.path.join(self.experiment_root,"configs")) + offset = 0 + for _ in range(self.generated_configs // self.__max_array_size): + with open(os.path.join(self.experiment_root, f'slurm{self.__number_of_slurm_scripts}.sh'), 'w') as f: + f.write(script\ + .replace('##from_number##', str(offset))\ + .replace('##jobs_count##', str(self.__max_array_size - 1))) + offset += self.__max_array_size + self.__number_of_slurm_scripts += 1 + r = self.generated_configs % self.__max_array_size + if r > 0: + with open(os.path.join(self.experiment_root, f'slurm{self.__number_of_slurm_scripts}.sh'), 'w') as f: + f.write(script\ + .replace('##from_number##', str(offset))\ + .replace('##jobs_count##', str(r - 1))) + offset += r + self.__number_of_slurm_scripts += 1 + + def __generate_configs(self, experiment_config_file_name): + with open(experiment_config_file_name, 'r') as f: + config = json.load(f) + self.result_folder_prefix = config['folder'] + fids = config['fids'] + iids = config['iids'] + dims = config['dims'] + reps = config['reps'] + logger_mode = config["logger"] + sample_zero = bool(config["sample_zero"]) + if 'extra' not in config.keys(): + config['extra'] = '' + optimizers = config['optimizers'] + lb, ub = config['lb'], config['ub'] + runs_number = len(optimizers) * len(fids) * len(iids) * len(dims) * reps + cur_config_number = 0 + configs_dir = os.path.join(self.experiment_root, 'configs') + os.makedirs(configs_dir, exist_ok=False) + with open(os.path.join(self.experiment_root, 'description.json'), 'w') as f: + json.dump(config, f, indent=4) + for my_optimizer_name in optimizers: + for fid in fids: + for iid in iids: + for dim in dims: + # print(f'Ids for opt={my_optimizer_name}, fid={fid}, iid={iid}, dim={dim} are [{cur_config_number}, {cur_config_number+reps-1}]') + for rep in range(reps): + experiment_config = { + 'folder': f'{self.result_folder_prefix}_Opt-{my_optimizer_name}_F-{fid}_Id-{iid}_Dim-{dim}_Rep-{rep}_NumExp-{cur_config_number}', + 'opt': my_optimizer_name, + 'fid': fid, + 'iid': iid, + 'dim': dim, + 'seed': rep, + 'lb': lb, + 'ub': ub, + 'logger':logger_mode, + 'sample_zero':int(sample_zero) + } + cur_config_file_name = f'Opt-{my_optimizer_name}_F-{fid}_Id-{iid}_Dim-{dim}_Rep-{rep}_NumExp-{cur_config_number}.json' + with open(os.path.join(configs_dir, cur_config_file_name), 'w') as f: + json.dump(experiment_config, f) + cur_config_number += 1 + print(f'Generated {cur_config_number} files') + self.generated_configs = cur_config_number + def is_slurm_available(self): + try: + subprocess.run(["sbatch", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + return True + except FileNotFoundError: + return False + + def print_helper(self): + if self.is_slurm_available(): + print(f'cd {self.experiment_root} && for (( i=0; i<{self.__number_of_slurm_scripts}; ++i )); do sbatch slurm$i.sh; done') + + +def main(argv): + env = ExperimentEnvironment() + env.set_up_by_experiment_config_file(argv[1]) + env.print_helper() + + +if __name__ == '__main__': + main(sys.argv) diff --git a/my_logger.py b/my_logger.py index fd65ad5..58aac45 100644 --- a/my_logger.py +++ b/my_logger.py @@ -1,110 +1,175 @@ -import os -import time -from ioh import get_problem - - -class MyIOHFormatOnEveryEvaluationLogger: - def __init__(self, folder_name='TMP', algorithm_name='UNKNOWN', suite='unkown suite', algorithm_info='algorithm_info'): - self.folder_name = MyIOHFormatOnEveryEvaluationLogger.__generate_dir_name(folder_name) - self.algorithm_name = algorithm_name - self.algorithm_info = algorithm_info - self.suite = suite - self.create_time = time.process_time() - - @staticmethod - def __generate_dir_name(name, x=0): - while True: - dir_name = (name + ('-' + str(x))).strip() - if not os.path.exists(dir_name): - os.mkdir(dir_name) - return dir_name - else: - x = x + 1 - - def watch(self, algorithm, extra_data): - self.algorithm = algorithm - self.extra_info_getters = extra_data - - def _set_up_logger(self, fid, iid, dim, func_name): - self.log_info_path = f'{self.folder_name}/IOHprofiler_f{fid}_{func_name}.info' - with open(self.log_info_path, 'a') as f: - f.write(f'suite = \"{self.suite}\", funcId = {fid}, funcName = \"{func_name}\", DIM = {dim}, maximization = \"F\", algId = \"{self.algorithm_name}\", algInfo = \"{self.algorithm_info}\"\n') - self.log_file_path = f'data_f{fid}_{func_name}/IOHprofiler_f{fid}_DIM{dim}.dat' - self.log_file_full_path = f'{self.folder_name}/{self.log_file_path}' - os.makedirs(os.path.dirname(self.log_file_full_path), exist_ok=True) - self.first_line = 0 - self.last_line = 0 - with open(self.log_file_full_path, 'a') as f: - f.write('\"function evaluation\" \"current f(x)\" \"best-so-far f(x)\" \"current af(x)+b\" \"best af(x)+b\"') - for extra_info in self.extra_info_getters: - f.write(f' {extra_info}') - f.write('\n') - - def log(self, cur_evaluation, cur_fitness, best_so_far, loss_f, best_loss_f): - with open(self.log_file_full_path, 'a') as f: - f.write(f'{cur_evaluation} {loss_f} {best_loss_f} {cur_fitness} {best_so_far}') - for fu in self.extra_info_getters: - try: - extra_info = getattr(self.algorithm, fu) - except Exception as e: - extra_info = 'None' - f.write(f' {extra_info}') - f.write('\n') - self.last_line += 1 - - def finish_logging(self): - time_taken = time.process_time() - self.create_time - with open(self.log_info_path, 'a') as f: - f.write('%\n') - f.write(f'{self.log_file_path}, {self.first_line}:{self.last_line}|{time_taken}\n') - - -class MyObjectiveFunctionWrapper: - def __init__(self, fid, iid, dim, directed_by='IOH'): - self.fid = fid - self.iid = iid - self.dim = dim - self.my_loggers = [] - if directed_by == 'Hao': - self.my_function, self.optimum = bn.instantiate(ifun=fid, iinstance=iid) - iohf = get_problem(fid, dimension=dim, instance=iid, problem_type = 'Real') - self.func_name = iohf.meta_data.name - elif directed_by == 'IOH': - self.my_function = get_problem(fid, dimension=dim, instance=iid, problem_type = 'Real') - self.func_name = self.my_function.meta_data.name - self.optimum = self.my_function.objective.y - else: - raise ValueError('Unknown way to create function using', directed_by) - self.cnt_eval = 0 - self.best_so_far = float('inf') - self.min_distance = float('inf') - self.best_loss = float('inf') - - # def __call__(self, x): - # cur_value = self.my_function(x) - # distance = cur_value - # self.best_so_far = min(self.best_so_far, cur_value) - # self.min_distance = min(self.min_distance, distance) - # self.cnt_eval += 1 - # for l in self.my_loggers: - # l.log(self.cnt_eval, distance, self.min_distance) - # return cur_value - - def __call__(self, x): - cur_value = self.my_function(x) - distance = cur_value - optimumy = self.optimum - loss = (cur_value - optimumy) - self.best_so_far = min(self.best_so_far, cur_value) - self.min_distance = min(self.min_distance, distance) - self.best_loss = min(self.best_loss,loss) - self.cnt_eval += 1 - for l in self.my_loggers: - l.log(self.cnt_eval, distance, self.min_distance, loss, self.best_loss) - return cur_value - - def attach_logger(self, logger): - self.my_loggers.append(logger) - logger._set_up_logger(self.fid, self.iid, self.dim, self.func_name) - - +import os +import time +from ioh import get_problem +import ioh +from typing import List, Optional, Union +from numpy import ndarray +from numpy import matrix +from numpy import ravel +import warnings +from typing import overload + + + + +class MyIOHFormatOnEveryEvaluationLogger: + def __init__(self, folder_name='TMP', algorithm_name='UNKNOWN', suite='unknown suite', algorithm_info='algorithm_info'): + self.folder_name = MyIOHFormatOnEveryEvaluationLogger.__generate_dir_name(folder_name) + self.algorithm_name = algorithm_name + self.algorithm_info = algorithm_info + self.suite = suite + self.create_time = time.process_time() + + @staticmethod + def __generate_dir_name(name, x=0): + while True: + dir_name = (name + ('-' + str(x))).strip() + if not os.path.exists(dir_name): + os.mkdir(dir_name) + return dir_name + else: + x = x + 1 + + def watch(self, algorithm, extra_data): + self.algorithm = algorithm + self.extra_info_getters = extra_data + + def _set_up_logger(self, fid, iid, dim, func_name): + self.log_info_path = f'{self.folder_name}/IOHprofiler_f{fid}_{func_name}.info' + with open(self.log_info_path, 'a') as f: + f.write(f'suite = \"{self.suite}\", funcId = {fid}, funcName = \"{func_name}\", DIM = {dim}, maximization = \"F\", algId = \"{self.algorithm_name}\", algInfo = \"{self.algorithm_info}\"\n') + self.log_file_path = f'data_f{fid}_{func_name}/IOHprofiler_f{fid}_DIM{dim}.dat' + self.log_file_full_path = f'{self.folder_name}/{self.log_file_path}' + os.makedirs(os.path.dirname(self.log_file_full_path), exist_ok=True) + self.first_line = 0 + self.last_line = 0 + with open(self.log_file_full_path, 'a') as f: + f.write('\"function evaluation\" \"current f(x)\" \"best-so-far f(x)\" \"current af(x)+b\" \"best af(x)+b\"') + for extra_info in self.extra_info_getters: + f.write(f' {extra_info}') + f.write('\n') + + def log(self, cur_evaluation, cur_fitness, best_so_far, loss_f, best_loss_f): + with open(self.log_file_full_path, 'a') as f: + f.write(f'{cur_evaluation} {loss_f} {best_loss_f} {cur_fitness} {best_so_far}') + for fu in self.extra_info_getters: + try: + extra_info = getattr(self.algorithm, fu) + except Exception as e: + extra_info = 'None' + f.write(f' {extra_info}') + f.write('\n') + self.last_line += 1 + + def finish_logging(self): + time_taken = time.process_time() - self.create_time + with open(self.log_info_path, 'a') as f: + f.write('%\n') + f.write(f'{self.log_file_path}, {self.first_line}:{self.last_line}|{time_taken}\n') + + +class MyIOHFormatOnEveryEvaluationLogger2(ioh.iohcpp.logger.AbstractLogger): + r""" + This is a handler to perform the same kind of logging performed by Maria Laura in order to follow the + IOH Analyzer guidelines. + """ + def __init__(self, + triggers = ..., + properties = ..., + root = ..., + folder_name = ..., + algorithm_name = ..., + algorithm_info = ..., + suite='unknown suite', + ):#store_positions = False): + r""" + Just the same constructor as the superclass (`ioh.logger.Analyzer`). + """ + super().__init__(triggers, properties) + + # Fill up the properties + self.root = root + self.folder_name = self.__generate_dir_name(folder_name) + self.algorithm_name =algorithm_name + self.algorithm_info = algorithm_info + self.suite = suite + + self._setup_generated:bool = False + self.create_time = time.process_time() + + def __call__(self, logInfo:ioh.iohcpp.LogInfo, *args, **kwds): + #print(logInfo,args,kwds) + #return super().__call__(*args, **kwds) + if not self._setup_generated: + self._set_up_logger(fid= self.problem.problem_id, + iid =self.problem.instance, + dim = self.problem.n_variables, + func_name=self.problem.name) + + self._setup_generated = True + + # log the info + self.log(logInfo.evaluations, + logInfo.y, + logInfo.y_best, + logInfo.y - logInfo.objective.y, + logInfo.y_best - logInfo.objective.y) + + + def __generate_dir_name(self,name, x=0): + while True: + dir_name = (name + ('-' + str(x))).strip() + if not os.path.exists(os.path.join(self.root,dir_name)): + os.mkdir(dir_name) + return dir_name + else: + x = x + 1 + + def _set_up_logger(self, fid, iid, dim, func_name): + self.log_info_path = f'{self.folder_name}/IOHprofiler_f{fid}_{func_name}.info' + with open(self.log_info_path, 'a') as f: + f.write(f'suite = \"{self.suite}\", funcId = {fid}, funcName = \"{func_name}\", DIM = {dim}, maximization = \"F\", algId = \"{self.algorithm_name}\", algInfo = \"{self.algorithm_info}\"\n') + self.log_file_path = f'data_f{fid}_{func_name}/IOHprofiler_f{fid}_DIM{dim}.dat' + self.log_file_full_path = f'{self.folder_name}/{self.log_file_path}' + os.makedirs(os.path.dirname(self.log_file_full_path), exist_ok=True) + self.first_line = 0 + self.last_line = 0 + with open(self.log_file_full_path, 'a') as f: + f.write('\"function evaluation\" \"current f(x)\" \"best-so-far f(x)\" \"current af(x)+b\" \"best af(x)+b\"') + for extra_info in self.extra_info_getters: + f.write(f' {extra_info}') + f.write('\n') + + def log(self, cur_evaluation, cur_fitness, best_so_far, loss_f, best_loss_f): + with open(self.log_file_full_path, 'a') as f: + f.write(f'{cur_evaluation} {loss_f} {best_loss_f} {cur_fitness} {best_so_far}') + for fu in self.extra_info_getters: + try: + extra_info = getattr(self.algorithm, fu) + except Exception as e: + extra_info = 'None' + f.write(f' {extra_info}') + f.write('\n') + self.last_line += 1 + + @overload + def watch(self, algorithm, extra_data)->None: ... + + def watch(self,obj:object,extra_props:list)->None: + self.algorithm = obj + self.extra_info_getters = extra_props + + def finish_logging(self): + time_taken = time.process_time() - self.create_time + with open(self.log_info_path, 'a') as f: + f.write('%\n') + f.write(f'{self.log_file_path}, {self.first_line}:{self.last_line}|{time_taken}\n') + + + + + + + + + diff --git a/mylib/lib_BAxUS/BAxUS/CONTRIBUTORS.md b/mylib/lib_BAxUS/BAxUS/CONTRIBUTORS.md new file mode 100644 index 0000000..8021e15 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/CONTRIBUTORS.md @@ -0,0 +1,7 @@ +Original TuRBO implementation written by: + +- David Eriksson + +BAxUS code written by: + +- Leonard Papenmeier diff --git a/mylib/lib_BAxUS/BAxUS/Dockerfile b/mylib/lib_BAxUS/BAxUS/Dockerfile new file mode 100644 index 0000000..1641c7c --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.10-buster +WORKDIR /app +COPY . . +RUN apt-get update && \ + apt-get -y upgrade &&\ + apt-get -y install libsuitesparse-dev libatlas-base-dev swig libopenblas-dev libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-2.0-0 libsdl2-ttf-2.0-0 libsdl2-dev &&\ + pip install --no-cache-dir -r requirements.txt + diff --git a/mylib/lib_BAxUS/BAxUS/LICENSE.md b/mylib/lib_BAxUS/BAxUS/LICENSE.md new file mode 100644 index 0000000..063c3cb --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/LICENSE.md @@ -0,0 +1,41 @@ +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by the text below. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under this License. + +This License governs use of the accompanying Work, and your use of the Work constitutes acceptance of this License. + +You may use this Work for any non-commercial purpose, subject to the restrictions in this License. Some purposes which can be non-commercial are teaching, academic research, and personal experimentation. You may also distribute this Work with books or other teaching materials, or publish the Work on websites, that are intended to teach the use of the Work. + +You may not use or distribute this Work, or any derivative works, outputs, or results from the Work, in any form for commercial purposes. Non-exhaustive examples of commercial purposes would be running business operations, licensing, leasing, or selling the Work, or distributing the Work for use with commercial products. + +You may modify this Work and distribute the modified Work for non-commercial purposes, however, you may not grant rights to the Work or derivative works that are broader than or in conflict with those provided by this License. For example, you may not distribute modifications of the Work under terms that would permit commercial use, or under terms that purport to require the Work or derivative works to be sublicensed to others. + +In return, we require that you agree: + +1. Not to remove any copyright or other notices from the Work. + +2. That if you distribute the Work in Source or Object form, you will include a verbatim copy of this License. + +3. That if you distribute derivative works of the Work in Source form, you do so only under a license that includes all of the provisions of this License and is not in conflict with this License, and if you distribute derivative works of the Work solely in Object form you do so only under a license that complies with this License. + +4. That if you have modified the Work or created derivative works from the Work, and distribute such modifications or derivative works, you will cause the modified files to carry prominent notices so that recipients know that they are not receiving the original Work. Such notices must state: (i) that you have changed the Work; and (ii) the date of any changes. + +5. If you publicly use the Work or any output or result of the Work, you will provide a notice with such use that provides any person who uses, views, accesses, interacts with, or is otherwise exposed to the Work (i) with information of the nature of the Work, (ii) with a link to the Work, and (iii) a notice that the Work is available under this License. + +6. THAT THE WORK COMES "AS IS", WITH NO WARRANTIES. THIS MEANS NO EXPRESS, IMPLIED OR STATUTORY WARRANTY, INCLUDING WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT. ALSO, YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +7. THAT NEITHER UBER TECHNOLOGIES, INC. NOR ANY OF ITS AFFILIATES, SUPPLIERS, SUCCESSORS, NOR ASSIGNS WILL BE LIABLE FOR ANY DAMAGES RELATED TO THE WORK OR THIS LICENSE, INCLUDING DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, TO THE MAXIMUM EXTENT THE LAW PERMITS, NO MATTER WHAT LEGAL THEORY IT IS BASED ON. ALSO, YOU MUST PASS THIS LIMITATION OF LIABILITY ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +8. That if you sue anyone over patents that you think may apply to the Work or anyone's use of the Work, your license to the Work ends automatically. + +9. That your rights under the License end automatically if you breach it in any way. + +10. Uber Technologies, Inc. reserves all rights not expressly granted to you in this License. diff --git a/mylib/lib_BAxUS/BAxUS/README.md b/mylib/lib_BAxUS/BAxUS/README.md new file mode 100644 index 0000000..4b8e1fe --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/README.md @@ -0,0 +1,273 @@ +# Increasing the Scope as You Learn: BAxUS + +> 💡 **_NOTE:_** If you're interested in BAxUS, please consider using [Bounce](https://github.com/LeoIV/bounce), which comes with an improved trust region management policy, an easier setup, and batch parallelism. + +![figure of splitting method](baxus_figure.png) + +This is the code for [our paper](https://openreview.net/pdf?id=e4Wf6112DI): +`Increasing the Scope as You Learn: Adaptive Bayesian Optimization in Nested Subspaces` (Leonard Papenmeier, Luigi Nardi, and Matthias +Poloczek) + +Please see the full [online documentation](https://baxus.papenmeier.io). + +## Installation + +You have four options for installing `BAxUS`: `PyPi`, `Docker`, `setup.py`, or `requirements.txt`. +Please make sure to install the following packages before running the non-Docker `BAxUS` installation. +We assume that you have a Debian Buster or Bullseye based Linux distribution (e.g., Ubuntu 18.04 or Ubuntu 20.04). +Please use a Docker image if you are working with a different distribution: + +### Installation from PyPi + +```bash +pip install baxus +``` + +### Installation from source + +First install required software: + +```bash +apt-get update && apt-get -y upgrade && apt-get -y install libsuitesparse-dev libatlas-base-dev swig libopenblas-dev libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-2.0-0 libsdl2-ttf-2.0-0 libsdl2-dev +``` + +Then install with the `setup.py`: + +```bash +cd baxus +pip install . +``` + +or with the requirements.txt: + +```bash +cd baxus +pip install -r requirements.txt +``` + +### Docker image + +Alternatively, use the Docker installation. +We do not share the Docker image to ensure anonymity. +However, you can build the Docker image yourself with the provided `Dockerfile`: + +First, [install Docker](https://docs.docker.com/engine/install/). +Next, build the Docker image + +```bash +cd baxus +sudo docker build -t baxus +``` + +By default, BAxUS stores all results in a directory called `results`. +To get the results on the host machine, first create this directory and mount it into the Docker container: + +```bash +mkdir results +sudo docker run -v "$(pwd)/results":/app/results baxus /bin/bash -c "python benchmark_runner.py -id 100 -td 1 -f branin2 --adjust-initial-target-dimension" +``` + +After the run completed, the results can be obtained in the `./results` directory. + +## Getting started + +The main file is `benchmark_runner.py` in the project root. +It can be configured with command line arguments (see [Command Line Options](README.html)) + +For example, to run `BAxUS` for 1,000 function evaluations on a Branin2 function with input dimensionality 100 for one +repetition run (for installation from source) + +```python +python3 +benchmark_runner.py - id +100 - td +1 - n +10 - r +1 - m +1000 - f +branin2 - a +baxus - -adjust - initial - target - dimension +``` + +or, for PyPi installations, + +```bash +benchmark_runner.py -id 100 -td 1 -n 10 -r 1 -m 1000 -f branin2 -a baxus --adjust-initial-target-dimension +``` + +For Docker, follow the instructions above. + +Note that we need to pass an initial target dimensionality with `-td 1` even though this is adjusted later by passing +the option `--adjust-initial-target-dimension`- + +## Command line options + +| **Name** | **Shortcut** | **Full argument** | **Default** | **Description** | +|--------------------------------------|--------------|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Algorithm | `-a` | `--algorithms` | `baxus` | The algorithm to run. Has tobe from `baxus`, `embedded_turbo_target_dim`, `embedded_turbo_effective_dim`, `embedded_turbo_2_effective_dim`, `random_search` | +| Function | `-f` | `--functions` | None | One ore several test functions. Has to be from `lunarlander`,`mnist`,`robotpushing`,`roverplanning`,`hartmann6`,`branin2`,`rosenbrock5`,`rosenbrock10`,`ackley`,`rosenbrock`,`levy`,`dixonprice`,`griewank`,`michalewicz`,`rastrigin`,`bipedalnncontroller`,`acrobotnncontroller`,`svm`,`lasso10`,`mopta08`,`hartmann6in1000_rotated`,`rosenbrock5in1000_rotated`. | +| Input dimensionality | `-id` | `--input-dim` | `100` | Input dimensionality of the function. This is overriden when the function has a fixed dimensionality. | +| Target dimensionality | `-td` | `--target-dim` | `10` | (Initial) targetdimensionality of the function. Whether initial or not depends on the algorithm. Initial for `BAxUS` as it adapts the target dimensionality. | | +| Acquisition function | None | `--acquisition-function` | `ts` | Either `ts` (Thompson sampling) or `ei` (Expected improvement) | +| Embedding type | None | `--embedding-type` | `baxus` | Either `baxus` (for the BAxUS embedding) or `hesbo` (for the HeSBO embedding) | +| Adjust initial target dimensionality | None | `--adjust-initial-target-dimension` | not set | Whether to adjust initial target dimensionality as described in the BAxUS paper. | +| Number of initial samples | `-n` | `--n-init` | None (set to target dimensionality + 1 if not set) | Number of samples. | +| Number of repetitions | `-r` | `--num-repetitions` | `1` | Number repetitions of the run. | +| Number of evaluations | `-m` | `--max-evals` | `300` | Number evaluations. Cma-ES might use a few more. | +| Initial baselength | `-l` | `--initial-baselength` | `0.8` | The base length of the trust region (default value is as in the TuRBO paper). | +| Minimum baselength | `-lmin` | `--min-baselength` | `0.5^7` | The base length a trust region is allowed to obtain (default value is as in the TuRBO paper). | +| Maximum baselength | `-l_max` | `--max-baselength` | 1.6 | The maximum base length a trust region is allowed obtain (default value is as in the TuRBO paper). | +| Noise standard deviation | None | `--noise-std` | `0` | The deviation of the noise. Whether this is used or not depends on the benchmark. It is generally only recognized synthetic benchmarks like `Branin2` but also for the synthetic `Lasso` versions. | +| Results directory | None | `--results-dir` | `results` | The directory which the results are written. Relative to the path from which the run was started. | +| Run description | None | `--run-description` | None | Short description that will be added to the run directory | +| MLE multistart samples | None | `--multistart-samples` | `100` | Number of multistart samples for the MLE GD optimization. Samples will be drawn from latin hypercube | +| Multistarts after sampling | None | `--multistart-after-sample` | `10` | Only recognized for `--mle-optimization sample-and-choose-best`. Number of multi-start gradient descent optimization of the `--multistart-samples` best ones. | | +| MLE optimization method | None | `--mle-optimization` | `sample-and-choose-best` Either `multistart-gd` or `sample-and-choose-best`. | | +| Number of MLE gradient updates | None | `--mle-training-steps` | `50` | Number of GD steps in MLE maximization. | | +| Budget until input dimensionality | None | `--budget-until-input-dim` | `0` | The budget after which BAxUS will roughly reach the input dimensionality (see paper for details). If `0`: this is ignored | | | | | +| Verbose mode | `-v` | `--verbose` | not set | Whether to print verbose messages | + +## Optimizing custom functions + +### Custom benchmark class + +For practical use cases, you want to optimize your own functions instead of running benchmark functions. Let's see how +we implement benchmark functions. As an example, +[MoptaSoftConstraints](source/baxus.benchmarks.html#baxus.benchmarks.real_world_benchmarks.MoptaSoftConstraints) +implements +[SyntheticBenchmark](source/baxus.benchmarks.html#baxus.benchmarks.benchmark_function.SyntheticBenchmark), which means +in particular that it has its +own `__call__` function. + +Let's look at the `__call__` function +of [MoptaSoftConstraints](source/baxus.benchmarks.html#baxus.benchmarks.real_world_benchmarks.MoptaSoftConstraints): + +```python +def __call__(self, x): + super(MoptaSoftConstraints, self).__call__(x) + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + + vals = np.array([self._call(y) for y in x]).squeeze() + return vals +``` + +which consists of some checks that ensure that we use the internal `self._call` function correctly. + +If you want to use BAxUS with a custom function, you can just use this implementation and replace +`self._call` in the line +`vals = np.array([self._call(y) for y in x]).squeeze()` +with a call to your own function expecting a 1D numpy array. + +### Example for a custom benchmark function + +A custom benchmark function could look as follows: + +```python3 +from typing import Union, List + +import numpy as np +from baxus.benchmarks.benchmark_function import Benchmark + + +class Parabula(Benchmark): + + def __init__(self): + super().__init__(dim=100, ub=10 * np.ones(100), lb=-10 * np.ones(100), noise_std=0) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + y = np.sum(x ** 2, axis=1) + return y +``` + +To run `BAxUS` on it, either register it for the benchmark runner (see explanation below), +or call `BAxUS` directly: + +```python3 +from baxus import BAxUS + +baxus = BAxUS( + run_dir="results", + max_evals=100, + n_init=10, + f=Parabula(), + target_dim=2, + verbose=True, +) + +baxus.optimize() + +``` + +The results of the optimization can afterwards be obtained by + +```python3 +x_raw, y_raw = baxus.optimization_results_raw() # get the points in the search space and their function values +x_inc, y_inc = baxus.optimization_results_incumbent() # get the points in the search space and the best function value at each time step +``` + +### How do I register my new function? + +For this we need to look at the [parsing.parse](source/baxus.util.html#baxus.util.parsing.parse) function. +The first thing to do is to append your benchmark to the list of existing benchmarks, +currently consisting of + +```python +required_named.add_argument( + "-f", + "--functions", + nargs="+", + choices=[ + "hartmann6", + "branin2", + ..., + "MY_NEW_NAME" # <---------------- ADD THIS LINE + ], + required=True, +) +``` + +Next, we have to register the new name in the [parsing.fun_mapper](source/baxus.util.html#baxus.util.parsing.fun_mapper) +function: + +```python +def fun_mapper(): + return { + **{ + "hartmann6": Hartmann6, + "branin2": Branin2, + "rosenbrock2": functools.partial(RosenbrockEffectiveDim, effective_dim=2), + ..., + "MY_NEW_NAME": MyBenchmarkImplementation # <--------- ADD THIS LINE + }, + ** _fun_mapper, + } +``` + +and that's it. + +## Citation + +Please cite [our paper](https://openreview.net/pdf?id=e4Wf6112DI) if you use the code: + +``` +@inproceedings{ +papenmeier2022increasing, +title={Increasing the Scope as You Learn: Adaptive Bayesian Optimization in Nested Subspaces}, +author={Leonard Papenmeier and Luigi Nardi and Matthias Poloczek}, +booktitle={Advances in Neural Information Processing Systems}, +editor={Alice H. Oh and Alekh Agarwal and Danielle Belgrave and Kyunghyun Cho}, +year={2022}, +url={https://openreview.net/forum?id=e4Wf6112DI} +} +``` diff --git a/mylib/lib_BAxUS/BAxUS/baxus/__init__.py b/mylib/lib_BAxUS/BAxUS/baxus/__init__.py new file mode 100644 index 0000000..b776a2b --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/__init__.py @@ -0,0 +1,2 @@ +from .embeddedturbo import EmbeddedTuRBO +from .baxus import BAxUS diff --git a/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..d539b3e Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/baxus.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/baxus.cpython-310.pyc new file mode 100644 index 0000000..b45a01d Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/baxus.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/benchmark_runner.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/benchmark_runner.cpython-310.pyc new file mode 100644 index 0000000..a9b37e6 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/benchmark_runner.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/embeddedturbo.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/embeddedturbo.cpython-310.pyc new file mode 100644 index 0000000..5f33630 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/embeddedturbo.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/gp.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/gp.cpython-310.pyc new file mode 100644 index 0000000..23e02ae Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/__pycache__/gp.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/baxus.py b/mylib/lib_BAxUS/BAxUS/baxus/baxus.py new file mode 100644 index 0000000..20539b8 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/baxus.py @@ -0,0 +1,629 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +# Derived from the TuRBO implementation (https://github.com/uber-research/TuRBO) +# Author: Leonard Papenmeier + +import base64 +import math +import pickle +from copy import deepcopy +from logging import info, debug, warning +from typing import Dict, Optional + +import numpy as np + +from baxus import EmbeddedTuRBO +from baxus.benchmarks.benchmark_function import Benchmark +from baxus.util.behaviors import BaxusBehavior +from baxus.util.behaviors.gp_configuration import GPBehaviour +from baxus.util.data_utils import join_data +from baxus.util.projections import AxUS, ProjectionModel +from baxus.util.utils import ( + one_around_origin_latin_hypercube, + from_1_around_origin, + star_string, +) + + +class BAxUS(EmbeddedTuRBO): + """ + BAxUS main class. + + Args: + f: the function to optimize + target_dim: the latent dimensionality + n_init: number of initial samples + max_evals: max number of function evaluations + behavior: behavior configuration + gp_behaviour: the behavior of the associated Gaussian Process + verbose: verbose logging model + use_ard: whether the GP should use an ARD kernel (yes this should be part of the gp_behavior) + max_cholesky_size: + dtype: the datatype (float32, float64) + run_dir: the directory to which to write the run results + conf_name: the name of the configuration of the optimization run + sample_zero: Sample Zero vector from initial LHS + """ + + def __init__( + self, + f: Benchmark, + target_dim: int, + n_init: int, + max_evals: int, + behavior: BaxusBehavior = BaxusBehavior(), + gp_behaviour: GPBehaviour = GPBehaviour(), + verbose=True, + use_ard=True, + max_cholesky_size=2000, + dtype="float64", + run_dir=".", + conf_name: Optional[str] = None, + sample_zero:Optional[bool] = False + ): + self.behavior = behavior + # need to set this here, so we can adjust the initial target dim before initializing super() + self._input_dim = f.dim + self._init_target_dim = target_dim + self.sample_zero = sample_zero + + if self.behavior.adjust_initial_target_dim: + target_dim = self._adjust_init_target_dim() + self._init_target_dim = target_dim + + super().__init__( + f=f, + target_dim=target_dim, + n_init=n_init, + max_evals=max_evals, + verbose=verbose, + use_ard=use_ard, + max_cholesky_size=max_cholesky_size, + gp_behaviour=gp_behaviour, + dtype=dtype, + run_dir=run_dir, + behavior=behavior, + conf_name=conf_name, + ) + self._target_dim_after_reset = self.target_dim + assert ( + self.length_init > 2 * self.length_min + ), f"Initial length {self.length_init} has to be larger than two times the minimum length {self.length_min}." + + self._axus_change_iterations = [] + self._split_points = [] + self._trust_region_restarts = [] + self._dim_in_iterations = {} + + self._data_dims = [] + + @property + def target_dim_increases(self) -> int: + """ + Returns the number of times the target dimensionality was increased. + This is not the current target dimensionality minus the initial target dimensionality. + + Returns: The number of times the target dimensionality was increased. + + """ + base = self.behavior.n_new_bins + 1 + return round(math.log(self.target_dim / self._init_target_dim, base)) + + @EmbeddedTuRBO.target_dim.setter + def target_dim(self, target_dim: int) -> None: + """ + Setter for target dimensionality. + + Args: + target_dim: the new target dimensionality + + Returns: None + + """ + self._dim_in_iterations[self.n_evals] = target_dim + self._target_dim = target_dim + + @property + def splits(self) -> int: + """ + The number of splits in the current trust region. + + Returns: The number of splits in the current trust region. + + """ + base = self.behavior.n_new_bins + 1 + return round(math.log(self.target_dim / self._target_dim_after_reset, base)) + + @property + def length_min(self) -> float: + """ + The minimum base length of the trust region. + + Returns: The minimum base length of the trust region. + + """ + return self._length_min + + @property + def length_max(self) -> float: + """ + The maximum base length of the trust region. + + Returns: The maximum base length of the trust region. + + """ + return self._length_max + + @property + def length_init(self) -> float: + """ + The initial base length of the trust region. + + Returns: The initial base length of the trust region. + + """ + return self._length_init + + @property + def evaluations_since_last_split(self) -> int: + """ + The number of function evaluations since the last split. + + Returns: The number of function evaluations since the last split. Total number of evaluations if there was no split yet. + + """ + return ( + self.n_evals - self._axus_change_iterations[-1] + if len(self._axus_change_iterations) > 0 + else self.n_evals + ) + + @property + def sample_zero(self)->bool: + """ + The indication to sample the zero vector during the first stage of the optimization. + + Returns: A boolean indicating to sample the zero + + """ + + return self.__sample_zero + + @sample_zero.setter + def sample_zero(self,new_sample:bool)->None: + """ + Setter to sample zero handle + + Returns: A boolean indicating to sample the zero + + """ + + new_sample = bool(new_sample) + + self.__sample_zero = new_sample + + + @property + def _evaluations_in_last_splits_in_tr(self) -> int: + """ + The evaluations spent in previous splits in the current trust region + + Returns: the evaluations spent in previous splits in the current trust region + + """ + split_points = np.array(self._split_points) + if len(self._trust_region_restarts) > 0: + split_points = split_points[split_points >= self._trust_region_restarts[-1]] + if len(split_points) == 0: + return 0 + else: + if len(self._trust_region_restarts) == 0: + return split_points[-1] + else: + return split_points[-1] - self._trust_region_restarts[-1] + + @property + def _dimension_importances(self) -> np.ndarray: + """ + The (inverse) dimension importances. This just returns the lengthscales of the GP ARD kernel. + + Returns: The (inverse) dimension importances. This just returns the lengthscales of the GP ARD kernel. + + """ + return np.array(self.lengthscales) + + @property + def _split_in_trust_region(self) -> int: + """ + The number of this split in the current trust region, i.e., if we just reset the trust region and haven't + split yet, this is 1. Then, after the first split, 2, etc. + + Returns: the number of this split + + """ + if len(self._trust_region_restarts) == 0: + # if the trust region was not yet restarted, just return the number of splits + return len(self._split_points) + 1 + else: + iteration_of_restart = self._trust_region_restarts[-1] + sp = np.array(self._split_points) + return len(sp[sp >= iteration_of_restart]) + 1 + + @property + def _init_dim_in_tr(self) -> int: + """ + The dim with which the current trust region started. + + Returns: The dim with which the current trust region started. + + """ + dim_in_iterations = self._dim_in_iterations + if len(dim_in_iterations) == 0: + # target dim was not yet adjusted + return self._init_target_dim + else: + eval_when_tr_started = 0 if len(self._trust_region_restarts) == 0 else self._trust_region_restarts[-1] + tr_adjust_iters = np.array(list(dim_in_iterations.keys())) + min_iter = min(tr_adjust_iters[tr_adjust_iters >= eval_when_tr_started]) + return self._dim_in_iterations[min_iter] + + @property + def _budget_lost_in_previous_trs(self) -> int: + """ + The number of function evaluations used in previous trust regions. + + Returns: The number of function evaluations used in previous trust regions. + + """ + return self.n_init if len(self._trust_region_restarts) == 0 else self._trust_region_restarts[-1] + + def _adjust_init_target_dim(self) -> int: + """ + Adjust the initial target dimension such that the final target dimension + is as close to the ambient dimensionality as possible given a fixed b. + + Returns: int: the adjusted initial target dimension. + + """ + + def ndiff(b, d0): + psi = 1 + desired_final_dim = self.input_dim + initial_target_dim = d0 + + base = psi * b + 1 + n = round(math.log(desired_final_dim / initial_target_dim, base)) + df_br = round(base ** n * initial_target_dim) + res = np.abs(df_br - desired_final_dim) + return res, n + + i_b, i_d0 = self.behavior.n_new_bins, self._init_target_dim + + def _fmin(d0): + return ndiff(b=i_b, d0=d0)[0] + + bounds = (2, i_b + 1) + + x_best = 1 + y_best = _fmin(x_best) + for j_d0 in range(bounds[0], bounds[1]): + if _fmin(j_d0) < y_best: + x_best = j_d0 + y_best = _fmin(j_d0) + + info(star_string( + f"Can reach a difference of {y_best} with init target dim of {x_best} after {ndiff(i_b, x_best)[1]} splits. Adjusting...")) + return x_best + + def _restart(self, length: float = None) -> None: + """ + Reset TR observations, reset counter, reset base length + + Args: + length: new base length after resetting, if not set, length_init will be used. + + """ + self._X = np.empty((0, self.target_dim)) + self._fX = np.empty((0, 1)) + + self.failcount = 0 + self.succcount = 0 + if length is None: + self.length = self.length_init + else: + self.length = length + + @property + def failtol(self) -> float: + """ + The fail tolerance for the BAxUS algorithm. + Is computed dynamically depending on the split we are in as the fail tolerance is dependent on the + current target dimensionality. + + Returns: the fail tolerance for the BAxUS algorithm + + """ + ft_max = np.max([4.0, self.target_dim]) + if self.target_dim == self.input_dim: + return ft_max + + desired_final_dim = self.input_dim + evaluation_budget = self.max_evals if self.behavior.budget_until_input_dim == 0 else self.behavior.budget_until_input_dim + evaluation_budget = evaluation_budget - self._budget_lost_in_previous_trs + + psi = 1 + new_bins_on_split = self.behavior.n_new_bins + _log_base = psi * new_bins_on_split + 1 + n = round(math.log(desired_final_dim / self._init_dim_in_tr, _log_base)) # splits + + def _budget(dim): + + return (evaluation_budget * dim * (1 - _log_base)) / (self._init_dim_in_tr * (1 - _log_base ** (n + 1))) + + budget = _budget(self.target_dim) + + del ( + psi, + new_bins_on_split, + evaluation_budget, + ) + + length_init = self.behavior.initial_base_length + + gamma = 2 * math.log(self.length_min / length_init, 0.5) + if gamma == 0: + return ft_max + ft = math.ceil(budget / gamma) + failtol = max(1, min(ft, ft_max)) + + return failtol + + def _adjust_length(self, fX_next) -> None: + """ + Adjust the base length of the current trust region depending on the outcome of the next evaluation. + If the next evaluation is better than the current, increase success count and potentially increase TR base length. + Otherwise, increase fail count and potentially decrease TR base length. + + Args: + fX_next: the function value of the next point + + """ + debug( + f"eval {self.n_evals}: length = {self.length}, failcount = {self.failcount} (failtol = {self.failtol}), " + f"succcount = {self.succcount} (succtol = {self.succtol})" + ) + prev_data = self._fX + + if np.min(fX_next) < np.min( + prev_data + ) - self.behavior.success_decision_factor * math.fabs(np.min(prev_data)): + debug(f"eval {self.n_evals}: increase success count") + self.succcount += 1 + self.failcount = 0 + else: + debug(f"eval {self.n_evals}: increase failure count") + self.succcount = 0 + self.failcount += 1 + if self.succcount == self.succtol: # Expand trust region + debug(f"eval {self.n_evals}: expanding trust region") + self.length = min([2.0 * self.length, self.length_max]) + self.succcount = 0 + elif self.failcount == self.failtol: # Shrink trust region + debug(f"eval {self.n_evals}: shrinking trust region") + self.length /= 2.0 + self.failcount = 0 + + self._log_property("length_history", f"{self.n_evals}:{self.length}") + + def _choose_splitting_dim( + self, + projector: AxUS, + ) -> Dict[int, int]: + """ + Choose a new splitting dim based on our defined behavior + + Args: + projector: the projection model used + + Returns: the new splitting dim or -1 if none could be found + + + """ + + n_dims_to_split = self.target_dim + + n_new_bins = self.behavior.n_new_bins + n_new_bins = (n_new_bins + 1) * n_dims_to_split + assert n_new_bins >= 2 * n_dims_to_split, ( + "Number of new bins has " + "to be at least 2 times" + "the number of dimensions" + "to split" + ) + weights = self._dimension_importances + indices_with_lengthscales = {i: weights[i] for i in range(self.target_dim)} + indices_sorted_by_lengthscales = sorted( + [i for i in indices_with_lengthscales.keys()], + key=lambda i: indices_with_lengthscales[i], + ) + splittable_idxs = np.array( + [ + i + for i in indices_sorted_by_lengthscales + if len(projector.contributing_dimensions(i)) > 1 + ] + ) + n_dims_to_split = min(len(splittable_idxs), n_dims_to_split) + if n_dims_to_split == 0: + return {} + n_bins_per_dim = n_new_bins // n_dims_to_split + bins_per_dim = np.array( + [ + min(n_bins_per_dim, len(projector.contributing_dimensions(i))) + for i in splittable_idxs + ] + ) + cum_sum = np.cumsum(bins_per_dim) + dims_to_split = np.sum(cum_sum <= n_new_bins) + dims_and_bins = { + splittable_idxs[i]: bins_per_dim[i] for i in range(dims_to_split) + } + + return dims_and_bins + + def _resample_and_restart(self, n_points: int, length: float = None) -> None: + """ + Resample new initial points and reset algorithm. + + Args: + n_points: number of new initial points + length: new base length after resetting + + Returns: None + + """ + # Initialize parameters + self._restart(length=length) + + # Generate and evaluate initial design points + n_pts = min(self.max_evals - self.n_evals, n_points) + X_init = one_around_origin_latin_hypercube(n_pts, self.target_dim, self.sample_zero) + + if isinstance(self.projector,AxUS): + X_init_up = from_1_around_origin( + self.projector.project_up(X_init.T).T, self.lb, self.ub + ) + else: + # This is an addition in case the Projector is not set + X_init_up = X_init.copy() + + + fX_init = np.array([[self.f(x)] for x in X_init_up]) + # Update budget and set as initial data for this TR + self.n_evals += n_pts + self._X = deepcopy(X_init) + self._fX = deepcopy(fX_init) + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_init_up))) + self.fX = np.vstack((self.fX, deepcopy(fX_init))) + + self._data_dims.extend([self.target_dim] * n_pts) + + @staticmethod + def _projector_as_base64(projector: ProjectionModel) -> str: + """ + Return the current projection model as a Base64 string. + Args: + projector: the projector to return as base64. + + Returns: the current projection model as a Base64 string. + + """ + if isinstance(projector, AxUS): + return base64.b64encode(pickle.dumps(projector)).decode("utf-8") + return "" + + def optimize(self) -> None: + """ + Run the optimization + + Returns: None + + """ + self._log_property( + "projectors", f"{self.n_evals}:{self._projector_as_base64(self.projector)}" + ) + + while self.n_evals < self.max_evals and not self._optimum_reached(): + n_pts = min(self.max_evals - self.n_evals, self.n_init) + # only executed if we already gathered data, i.e., not in the first run + if len(self._fX) > 1: + # target dim increase + n_evals, fbest = self.n_evals, self._fX.min() + info(f"{n_evals}) Restarting with fbest = {fbest:.4}") + + # Split target dimension, will be used if we made progress and if not -1 + dims_and_bins = self._choose_splitting_dim(self.projector) + # first_split = self.target_dim == self._init_target_dim # TODO remove + + if dims_and_bins: # if we have a remaining-splitting dim + splitting_dims = list(dims_and_bins.keys()) + n_new_bins = sum(list(dims_and_bins.values())) + self._log_property( + "splitting_dims", + f"{self.target_dim_increases}:{','.join([str(x) for x in splitting_dims])}", + ) + self._log_property("split_points", f"{self.n_evals}") + self._split_points.append(self.n_evals) + for splitting_dim, n_bins in dims_and_bins.items(): + info( + f"eval {self.n_evals}: splitting dimension {splitting_dim + 1} into {n_bins} new " + f"bins with lengthscale: {self.lengthscales[splitting_dim]:.4} and contributing input " + f"dimensions {sorted(self.projector.contributing_dimensions(splitting_dim))}" + ) + self.projector.increase_target_dimensionality(dims_and_bins) + # self.projector.merge_dims(*np.argsort(-dim_ent)[:2]) + self._log_property( + "projectors", + f"{self.n_evals}:{self._projector_as_base64(self.projector)}", + ) + self.target_dim += n_new_bins - len(dims_and_bins) + self._dim_in_iterations[self.n_evals] = self.target_dim + info( + f"eval {self.n_evals}: new target dim = {self.target_dim}" + ) + self._axus_change_iterations.append(self.n_evals) + self.length = self.behavior.initial_base_length + + self._X = join_data(self._X, dims_and_bins) + + else: + warning( + f"eval {self.n_evals}: cannot increase further. " + f"Re-starting with new HeSBO embedding and new TR." + ) + self._log_property("tr_die_outs", f"{self.n_evals}") + self.projector = AxUS( + input_dim=self._input_dim, + target_dim=self.target_dim, + bin_sizing=self.behavior.embedding_type, + ) + self._log_property( + "projectors", + f"{self.n_evals}:{self._projector_as_base64(self.projector)}", + ) + self._resample_and_restart( + n_points=self.n_init, length=self.length_init + ) + self._axus_change_iterations.append(self.n_evals) + self._trust_region_restarts.append(self.n_evals) + self._dim_in_iterations[self.n_evals] = self.target_dim + + self.failcount = 0 + self.succcount = 0 + else: + self._resample_and_restart(self.n_init, self.length_init) + fbest = self._fX.min() + info(f"eval {self.n_evals}: starting from fbest = {fbest:.4}") + + # Thompson sample to get next suggestions + + while ( + self.n_evals < self.max_evals + and self.length >= self.length_min + and not self._optimum_reached() + ): + X_next, X_next_up, fX_next = self._inner_optimization_step() + self._data_dims.extend([self.target_dim] * len(X_next)) + self._optimized = True + self._log_property("final_target_dim", self.target_dim) diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmark_runner.py b/mylib/lib_BAxUS/BAxUS/baxus/benchmark_runner.py new file mode 100644 index 0000000..40c5698 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/benchmark_runner.py @@ -0,0 +1,339 @@ +import json +import logging +import os +import sys +from datetime import datetime +from logging import info, warning +from typing import List +from zlib import adler32 + +from baxus import EmbeddedTuRBO, BAxUS +from baxus.benchmarks import run_and_plot, EffectiveDimBenchmark, EffectiveDimBoTorchBenchmark, RandomSearch +from baxus.util.behaviors import BaxusBehavior, EmbeddedTuRBOBehavior +from baxus.util.behaviors.gp_configuration import GPBehaviour +from baxus.util.exceptions import ArgumentError +from baxus.util.parsing import parse, embedding_type_mapper, acquisition_function_mapper, mle_optimization_mapper, \ + fun_mapper +from baxus.util.utils import star_string + +FORMAT = "%(asctime)s %(levelname)s: %(filename)s: %(message)s" +DATEFORMAT = '%m/%d/%Y %I:%M:%S %p' + + +def main(argstring: List[str]) -> None: + """ + Parse the argstring and run algorithms based on the definition. + + .. note:: + This function should not be called directly but is called by benchmark_runner.py in the project root. + + Args: + argstring: the argument string + + Returns: Nothing + + """ + args = parse(argstring) + directory = os.path.join( + args.results_dir, + f"{datetime.now().strftime('%d_%m_%Y')}{f'-{args.run_description}' if len(args.run_description) > 0 else ''}", + ) + os.makedirs(directory, exist_ok=True) + logging.basicConfig( + filename=os.path.join(directory, "logging.log"), + level=logging.INFO if not args.verbose else logging.DEBUG, + format=FORMAT, + force=True, + datefmt=DATEFORMAT + ) + + sysout_handler = logging.StreamHandler(sys.stdout) + sysout_handler.setFormatter(logging.Formatter(fmt=FORMAT, datefmt=DATEFORMAT)) + logging.getLogger().addHandler(sysout_handler) + + repetitions = list(range(args.num_repetitions)) + + args_dict = vars(args) + with open(os.path.join(directory, "conf.json"), "w") as f: + f.write(json.dumps(args_dict)) + + bin_sizing_method = embedding_type_mapper[args.embedding_type] + + acquisition_function = acquisition_function_mapper[args.acquisition_function] + + mle_optimization_method = mle_optimization_mapper[args.mle_optimization] + + input_dim = args.input_dim + target_dim = args.target_dim + n_init = args.n_init + max_evals = args.max_evals + noise_std = args.noise_std + new_bins_on_split = args.new_bins_on_split + multistart_samples = args.multistart_samples + mle_training_steps = args.mle_training_steps + multistart_after_samples = args.multistart_after_sample + l_init = args.initial_baselength + l_min = args.min_baselength + l_max = args.max_baselength + adjust_initial_target_dim = args.adjust_initial_target_dimension + budget_until_input_dim = args.budget_until_input_dim + + combs = {} + + if n_init is None: + n_init = target_dim + 1 + if args.min_baselength > args.max_baselength: + raise ArgumentError( + "Minimum baselength has to be larger than maximum baselength." + ) + if args.input_dim < args.target_dim: + raise ArgumentError( + "Input dimension has to be larger than target dimension." + ) + if args.noise_std < 0: + raise ArgumentError("Noise standard deviation has to be positive.") + if max_evals < budget_until_input_dim: + raise ArgumentError("budget_until_input_dim has to be <= max_evals.") + if args.multistart_samples < 1: + raise ArgumentError("Number of multistart samples has to be >= 1.") + if args.multistart_after_sample > args.multistart_samples: + raise ArgumentError( + f"Number of multistart samples after sampling {args.multistart_after_sample} has to be smaller or equal to the numbers" + f"of initial multistart samples {args.multistart_samples}." + ) + if args.multistart_after_sample < 1: + raise ArgumentError( + "Number of multistart samples after sampling has to be >= 1." + ) + if args.mle_training_steps < 0: + raise ArgumentError("Number of mle training steps has to be >= 0.") + if new_bins_on_split < 2: + raise ArgumentError("Number of new bins on split has to be greater than one.") + + funs = { + k: v(dim=input_dim, noise_std=noise_std) + for k, v in fun_mapper().items() + if k == args.function + } + + c = { + f"{k}_in_dim_{v.dim}_t_dim{target_dim}_n_init_{n_init}" + f"{f'_noise_{noise_std}' if noise_std > 0 else ''}": { + "input_dim": v.dim, + "target_dim": min(v.dim, target_dim), + "n_init": n_init, + "f": v, + "lb": v.lb_vec, + "ub": v.ub_vec, + } + for k, v in funs.items() + } + + combs.update(c) + + for i, (k, comb) in enumerate(combs.items()): + info(f"running combination {k}") + llb = comb["lb"] + uub = comb["ub"] + input_dim = comb["input_dim"] + target_dim = comb["target_dim"] + n_init = comb["n_init"] + + f = comb["f"] + + function_dir = os.path.join(directory, k) + os.makedirs(function_dir, exist_ok=True) + + if "baxus" == args.algorithm: + # *** BAxUS *** + info("*** BAxUS***") + behavior = BaxusBehavior( + n_new_bins=new_bins_on_split, + initial_base_length=l_init, + min_base_length=l_min, + max_base_length=l_max, + acquisition_function=acquisition_function, + embedding_type=bin_sizing_method, + adjust_initial_target_dim=adjust_initial_target_dim, + noise=noise_std, + budget_until_input_dim=budget_until_input_dim + ) + gp_behaviour = GPBehaviour( + mll_estimation=mle_optimization_method, + n_initial_samples=multistart_samples, + n_best_on_lhs_selection=multistart_after_samples, + n_mle_training_steps=mle_training_steps, + ) + conf_name = ( + f"baxus_{behavior}_{gp_behaviour}" + ) + run_dir = os.path.join( + function_dir, + str(adler32(conf_name.encode("utf-8"))), + ) + baxus = BAxUS( + f=f, # Handle to objective function + n_init=n_init, # Number of initial bounds from an Latin hypercube design + max_evals=max_evals, # Maximum number of evaluations + target_dim=target_dim, + run_dir=run_dir, + conf_name=conf_name, + behavior=behavior, + gp_behaviour=gp_behaviour, + ) + run_and_plot(m=baxus, repetitions=repetitions, directory=run_dir) + del baxus + + if "embedded_turbo_target_dim" == args.algorithm: + # *** Embedded TuRBO - Target dim *** + info("*** Embedded TuRBO - Target Dim ***") + behavior = EmbeddedTuRBOBehavior( + initial_base_length=l_init, + min_base_length=l_min, + max_base_length=l_max, + acquisition_function=acquisition_function, + embedding_type=bin_sizing_method, + noise=noise_std, + ) + gp_behaviour = GPBehaviour( + mll_estimation=mle_optimization_method, + n_initial_samples=multistart_samples, + n_best_on_lhs_selection=multistart_after_samples, + n_mle_training_steps=mle_training_steps, + ) + conf_name = ( + f"embedded_turbo_target_dim_{behavior}" + f"_{gp_behaviour}" + ) + run_dir = os.path.join( + function_dir, + str(adler32(conf_name.encode("utf-8"))), + ) + embedded_turbo = EmbeddedTuRBO( + f=f, # Handle to objective function + target_dim=target_dim, + n_init=n_init, # Number of initial bounds from an Latin hypercube design + max_evals=max_evals, # Maximum number of evaluations + run_dir=run_dir, + conf_name=conf_name, + behavior=behavior, + gp_behaviour=gp_behaviour, + ) + + run_and_plot( + m=embedded_turbo, + repetitions=repetitions, + directory=run_dir, + ) + del embedded_turbo + + if "embedded_turbo_effective_dim" == args.algorithm: + if issubclass(type(f), EffectiveDimBenchmark) or issubclass( + type(f), EffectiveDimBoTorchBenchmark + ): + effective_dim = f.effective_dim + else: + warning("Benchmark with unknown effective dim. Choosing input dim.") + effective_dim = f.dim + + info("*** Embedded TuRBO - Effective Dim ***") + behavior = EmbeddedTuRBOBehavior( + initial_base_length=l_init, + min_base_length=l_min, + max_base_length=l_max, + acquisition_function=acquisition_function, + embedding_type=bin_sizing_method, + noise=noise_std, + ) + gp_behaviour = GPBehaviour( + mll_estimation=mle_optimization_method, + n_initial_samples=multistart_samples, + n_best_on_lhs_selection=multistart_after_samples, + n_mle_training_steps=mle_training_steps, + ) + conf_name = ( + f"embedded_turbo_effective_dim_{behavior}" + f"_{gp_behaviour}" + ) + run_dir = os.path.join( + function_dir, + str(adler32(conf_name.encode("utf-8"))), + ) + embedded_turbo = EmbeddedTuRBO( + f=f, + target_dim=effective_dim, + n_init=n_init, + max_evals=max_evals, + run_dir=run_dir, + conf_name=conf_name, + behavior=behavior, + gp_behaviour=gp_behaviour, + ) + run_and_plot( + m=embedded_turbo, + repetitions=repetitions, + directory=run_dir, + ) + del embedded_turbo + + if "embedded_turbo_2_effective_dim" == args.algorithm and ( + issubclass(type(f), EffectiveDimBenchmark) + or issubclass(type(f), EffectiveDimBoTorchBenchmark) + ): + info( + f"*** Embedded TuRBO- 2 * Effective Dim ***" + ) + behavior = EmbeddedTuRBOBehavior( + initial_base_length=l_init, + min_base_length=l_min, + max_base_length=l_max, + acquisition_function=acquisition_function, + embedding_type=bin_sizing_method, + noise=noise_std, + ) + gp_behaviour = GPBehaviour( + mll_estimation=mle_optimization_method, + n_initial_samples=multistart_samples, + n_best_on_lhs_selection=multistart_after_samples, + n_mle_training_steps=mle_training_steps, + ) + conf_name = ( + f"embedded_turbo_2_times_effective_dim_{behavior}" + f"_{gp_behaviour}" + ) + run_dir = os.path.join( + function_dir, + str(adler32(conf_name.encode("utf-8"))), + ) + embedded_turbo = EmbeddedTuRBO( + f=f, + target_dim=2 * f.effective_dim, + n_init=n_init, + max_evals=max_evals, + run_dir=run_dir, + conf_name=conf_name, + behavior=behavior, + gp_behaviour=gp_behaviour, + ) + run_and_plot( + m=embedded_turbo, + repetitions=repetitions, + directory=run_dir, + ) + del embedded_turbo + + if "random_search" == args.algorithm: + info(star_string("Random Search")) + rs = RandomSearch( + function=f, + lower_bounds=llb, + upper_bounds=uub, + run_dir=os.path.join(function_dir, "random_search"), + max_evals=max_evals, + input_dim=input_dim + ) + run_and_plot( + m=rs, + repetitions=repetitions, + directory=os.path.join(function_dir, "random_search") + ) diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__init__.py b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__init__.py new file mode 100644 index 0000000..1af17f6 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__init__.py @@ -0,0 +1,12 @@ +from .benchmark_function import Benchmark, BoTorchFunctionBenchmark, EffectiveDimBoTorchBenchmark, SyntheticBenchmark, \ + EffectiveDimBenchmark, SyntheticTestFunction +from .benchmark_utils import run_and_plot +from .other_methods import OptimizationMethod, RandomSearch + +from .real_world_benchmarks import SVMBenchmark, LassoHighBenchmark, LassoHardBenchmark, LassoDiabetesBenchmark, \ + LassoLeukemiaBenchmark, LassoMediumBenchmark, LassoSimpleBenchmark, LassoDNABenchmark, LassoRCV1Benchmark, \ + LassoBreastCancerBenchmark, MoptaSoftConstraints + +from .synthetic_benchmark_functions import BraninEffectiveDim, RosenbrockEffectiveDim, MichalewiczEffectiveDim, \ + HartmannEffectiveDim, LevyEffectiveDim, AckleyEffectiveDim, GriewankEffectiveDim, RastriginEffectiveDim, \ + DixonPriceEffectiveDim, RotatedHartmann6, ShiftedAckley10 diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..d310ff0 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/benchmark_function.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/benchmark_function.cpython-310.pyc new file mode 100644 index 0000000..af74e69 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/benchmark_function.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/benchmark_utils.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/benchmark_utils.cpython-310.pyc new file mode 100644 index 0000000..c9363b1 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/benchmark_utils.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/other_methods.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/other_methods.cpython-310.pyc new file mode 100644 index 0000000..5967fa9 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/other_methods.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/real_world_benchmarks.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/real_world_benchmarks.cpython-310.pyc new file mode 100644 index 0000000..f42d7c5 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/real_world_benchmarks.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/synthetic_benchmark_functions.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/synthetic_benchmark_functions.cpython-310.pyc new file mode 100644 index 0000000..103066e Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/__pycache__/synthetic_benchmark_functions.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/benchmark_function.py b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/benchmark_function.py new file mode 100644 index 0000000..213acf7 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/benchmark_function.py @@ -0,0 +1,343 @@ +from abc import ABC, abstractmethod +from logging import info +from typing import Optional, Union, List, Type + +import numpy as np +import torch +from botorch.test_functions import SyntheticTestFunction + +from baxus.util.exceptions import EffectiveDimTooLargeException, BoundsMismatchException, OutOfBoundsException + + +class Benchmark(ABC): + """ + Abstract benchmark function. + + Args: + dim: dimensionality of the objective function + noise_std: the standard deviation of the noise (None means no noise) + ub: the upper bound, the object will have the attribute ub_vec which is an np array of length dim filled with ub + lb: the lower bound, the object will have the attribute lb_vec which is an np array of length dim filled with lb + benchmark_func: the benchmark function, should inherit from SyntheticTestFunction + """ + + def __init__(self, dim: int, ub: np.ndarray, lb: np.ndarray, noise_std: float): + + lb = np.array(lb) + ub = np.array(ub) + if ( + not lb.shape == ub.shape + or not lb.ndim == 1 + or not ub.ndim == 1 + or not dim == len(lb) == len(ub) + ): + raise BoundsMismatchException() + if not np.all(lb < ub): + raise OutOfBoundsException() + self.noise_std = noise_std + self._dim = dim + self._lb_vec = lb.astype(np.float32) + self._ub_vec = ub.astype(np.float32) + + @property + def dim(self) -> int: + """ + The benchmark dimensionality + + Returns: the benchmark dimensionality + + """ + return self._dim + + @property + def lb_vec(self) -> np.ndarray: + """ + The lower bound of the search space of this benchmark (length = benchmark dim) + + Returns: The lower bound of the search space of this benchmark (length = benchmark dim) + + """ + return self._lb_vec + + @property + def ub_vec(self) -> np.ndarray: + """ + The upper bound of the search space of this benchmark (length = benchmark dim) + + Returns: The upper bound of the search space of this benchmark (length = benchmark dim) + + """ + return self._ub_vec + + @property + def fun_name(self) -> str: + """ + The name of the benchmark function + + Returns: The name of the benchmark function + + """ + return self.__class__.__name__ + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + raise NotImplementedError() + + +class SyntheticBenchmark(Benchmark): + """ + Abstract class for synthetic benchmarks + + Args: + dim: the benchmark dimensionality + ub: np.ndarray: the upper bound of the search space of this benchmark (length = benchmark dim) + lb: np.ndarray: the lower bound of the search space of this benchmark (length = benchmark dim) + """ + + @abstractmethod + def __init__(self, dim: int, ub: np.ndarray, lb: np.ndarray, noise_std: float): + super().__init__(dim, ub, lb, noise_std=noise_std) + + @abstractmethod + def __call__( + self, x: Union[np.ndarray, List[float], List[List[float]]] + ) -> np.ndarray: + """ + Call the benchmark function for one or multiple points. + + Args: + x: Union[np.ndarray, List[float], List[List[float]]]: the x-value(s) to evaluate. numpy array can be 1 or 2-dimensional + + Returns: + np.ndarray: The function values. + + + """ + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + # for y in x: + # if not np.sum(y < self._lb_vec) == 0: + # raise OutOfBoundsException() + # if not np.sum(y > self._ub_vec) == 0: + # raise OutOfBoundsException + + @property + def optimal_value(self) -> Optional[np.ndarray]: + """ + + Returns: + Optional[Union[float, np.ndarray]]: the optimal value if known + + """ + return None + + +class EffectiveDimBenchmark(SyntheticBenchmark): + """ + A benchmark with a known effective dimensionality. + + .. note:: + This is an abstract class that needs an implementation. + + + Args: + dim: the overall dimensionality of the problem + effective_dim: the effective dimensionality of the problem + ub: the upper bounds of the search space + lb: the lower bounds of the search space + noise_std: the noise std for this benchmark + """ + + def __init__( + self, + dim: int, + effective_dim: int, + ub: np.ndarray, + lb: np.ndarray, + noise_std: float, + ): + super().__init__(dim, ub, lb, noise_std=noise_std) + self.effective_dim: int = effective_dim + + @abstractmethod + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + raise NotImplementedError() + + +class BoTorchFunctionBenchmark(SyntheticBenchmark): + """ + A benchmark function that calls a BoTorch benchmark function + + Args: + dim: dimensionality of the problem + noise_std: noise std of the function + ub: the upper bound of the search space + lb: the lower bound of the search space + benchmark_func: the BoTorch benchmark function + """ + + def __init__( + self, + dim: int, + noise_std: Optional[float], + ub: np.ndarray, + lb: np.ndarray, + benchmark_func: Type[SyntheticTestFunction], + ): + super().__init__(dim, ub=ub, lb=lb, noise_std=noise_std) + try: + self._benchmark_func = benchmark_func(dim=dim, noise_std=noise_std) + except: + self._benchmark_func = benchmark_func(noise_std=noise_std) + + @property + def effective_dim(self) -> int: + """ + The effective dimensionality of the benchmark. + + Returns: The effective dimensionality of the benchmark. + + """ + return self._dim + + @property + def optimal_value(self) -> np.ndarray: + """ + The optimal value of the benchmark function. + + Returns: The optimal value of the benchmark function. + + """ + return self._benchmark_func.optimal_value + + def __call__(self, x: np.ndarray) -> np.ndarray: + """ + Call the function + + Args: + x: points at which to evaluate the function + + Returns: function value(s) + + """ + super(BoTorchFunctionBenchmark, self).__call__(x) + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + res = ( + self._benchmark_func.forward( + torch.tensor(np.clip(x, self._lb_vec, self._ub_vec)) + ) + .numpy() + .squeeze() + ) + return res + + +class EffectiveDimBoTorchBenchmark(BoTorchFunctionBenchmark): + """ + A benchmark class for synthetic benchmarks with a known effective dimensionality that are based on a BoTorch + implementation. + + Args: + dim: int: the ambient dimensionality of the benchmark + noise_std: float: standard deviation of the noise of the benchmark function + effective_dim: int: the desired effective dimensionality of the benchmark function + ub: np.ndarray: the upper bound of the benchmark search space. length = dim + lb: np.ndarray: the lower bound of the benchmark search space. length = dim + benchmark_func: Type[SyntheticTestFunction]: the BoTorch benchmark function to use + seed: int: random seed + """ + + def __init__( + self, + dim: int, + noise_std: Optional[float], + effective_dim: int, + ub: np.ndarray, + lb: np.ndarray, + benchmark_func: Type[SyntheticTestFunction], + seed: int = 123, + ): + super().__init__( + effective_dim, noise_std, ub=ub, lb=lb, benchmark_func=benchmark_func + ) + if effective_dim > dim: + raise EffectiveDimTooLargeException() + state = np.random.get_state() + np.random.seed(seed) + self._fake_dim = dim + self._effective_dim = effective_dim + self.effective_dims = np.arange(dim)[:effective_dim] + info(f"effective dims: {list(self.effective_dims)}") + np.random.set_state(state) + + def __call__(self, x, offset: np.ndarray = None) -> np.ndarray: + """ + Call the function + + Args: + x: points at which to evaluate the function + offset: offset to add to the x values (we used this for our ablation study with ShiftedAckley10) + + Returns: the function value(s) + + """ + if offset is None: + res = super(EffectiveDimBoTorchBenchmark, self).__call__( + x.squeeze().T[self.effective_dims].T + ) + else: + res = super(EffectiveDimBoTorchBenchmark, self).__call__( + x.squeeze().T[self.effective_dims].T + offset + ) + return res + + @property + def dim(self): + """ + The dimensionality of the problem. + + Returns: The dimensionality of the problem. + + """ + return self._fake_dim + + @property + def effective_dim(self) -> int: + """ + the effective dimensionality of the benchmark + + Returns: the effective dimensionality of the benchmark + + """ + return self._effective_dim + + @property + def lb_vec(self) -> np.ndarray: + """ + The lower bounds of the search space. + + Returns: The lower bounds of the search space. + + """ + return np.full( + shape=self._fake_dim, fill_value=np.min(self._lb_vec), dtype=np.float32 + ) + + @property + def ub_vec(self) -> np.ndarray: + """ + The upper bounds of the search space. + + Returns: The upper bounds of the search space. + + """ + return np.full( + shape=self._fake_dim, fill_value=np.max(self._ub_vec), dtype=np.float32 + ) diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/benchmark_utils.py b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/benchmark_utils.py new file mode 100644 index 0000000..66829fc --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/benchmark_utils.py @@ -0,0 +1,63 @@ +import os +from copy import deepcopy, copy +from logging import warning, info +from typing import List + +import numpy as np +import pandas as pd + + +MAX_RETRIES = 1 + + +def run_and_plot( + m: "baxus.benchmarks.OptimizationMethod", + repetitions: List[int], + directory: str, +) -> None: + """ + Run an experiment for a certain number of repetitions and save the results + + Args: + m: the experiment to runm_x + repetitions: the repetitions to run + directory: the directory to save the results + + Returns: + None + """ + os.makedirs(directory, exist_ok=True) + + base_run_dir = copy(m.run_dir) + + for rep in repetitions: + out_path = os.path.join(directory, f"repet_{rep}.csv.xz") + rep_run_dir = os.path.join(base_run_dir, f"repetition_{rep}") + os.makedirs(rep_run_dir, exist_ok=True) + m.run_dir = rep_run_dir + + if os.path.exists(out_path): + continue + info(f"starting repetition {rep}") + for mr in range(MAX_RETRIES): + try: + _m = deepcopy(m) + _m.reset() + _m.optimize() + break + except Exception as e: + if mr == MAX_RETRIES - 1: + raise e + warning(f"Optimization failed. Retrying... ({mr + 1}/{MAX_RETRIES})") + m_x, m_y_raw = _m.optimization_results_raw() + _, m_y = _m.optimization_results_incumbent() + m_y_raw = np.expand_dims(m_y_raw, axis=1) + if m_x is not None: + columns = [f"x{i}" for i in range(m_x.shape[1])] + ["y_raw"] + r_df = pd.DataFrame(np.concatenate((m_x, m_y_raw), axis=1), columns=columns) + else: + columns = [["y_raw"]] + r_df = pd.DataFrame(m_y_raw, columns=columns) + r_df.to_csv(out_path) + del r_df + del m_y_raw diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/other_methods.py b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/other_methods.py new file mode 100644 index 0000000..117c591 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/other_methods.py @@ -0,0 +1,149 @@ +import json +import os +from abc import ABC, abstractmethod +from logging import warning +from typing import Tuple, Optional, Dict, Any + +import numpy as np + +from baxus.benchmarks import Benchmark + + +class OptimizationMethod(ABC): + def __init__( + self, + run_dir: str, + conf_name: Optional[str] = None, + ): + """ + Abstract base class for a generic optimization method. + + Args: + run_dir: the directory to store results in + conf_name: the algorithm configuration to save to disk + """ + if not os.path.exists(run_dir): + os.makedirs(run_dir, exist_ok=True) + if conf_name is not None: + with open(os.path.join(run_dir, "conf_name.txt"), "w+") as f: + f.write(conf_name) + with open(os.path.join(run_dir, "conf_dict.json"), "w+") as f: + json.dump(self.conf_dict, f) + + self._optimized = False + self.run_dir = run_dir + + @abstractmethod + def optimize(self) -> None: + """ + Start the optimization. + + Returns: None + + """ + raise NotImplementedError() + + @abstractmethod + def optimization_results_raw( + self, + ) -> Tuple[Optional[np.ndarray], np.ndarray]: + """ + Get the raw optimization results, i.e., the x-values, the true function values, and the additional + run information. + + Returns: + tuple[X's, y's, additional_run_information] + """ + raise NotImplementedError() + + def reset(self) -> None: + warning("No reset implemented.") + pass + + @property + def conf_dict(self) -> Dict[str, Any]: + return {} + + def optimization_results_incumbent(self) -> Tuple[np.ndarray, np.ndarray]: + """ + Get the incumbent optimization results, i.e., optimization results such that y_2 is always less or equal to + y_1. + + Returns: + np.ndarray: the x-values + np.ndarray: the incumbent y-values + + """ + assert self._optimized, "Model hasn't been optimized yet" + ( + Xs, + ys, + ) = self.optimization_results_raw() + assert ys.ndim == 1 + ys_incumbent = np.minimum.accumulate(ys) + return Xs, ys_incumbent + + +class RandomSearch(OptimizationMethod): + def __init__( + self, + function: Benchmark, + input_dim: int, + max_evals: int, + run_dir: str, + lower_bounds: np.ndarray, + upper_bounds: np.ndarray): + """ + Simple random search implementation, samples points uniformly at random in the search space. + + Args: + function: the function to optimize + input_dim: the dimensionality of the problem + max_evals: maximum number of function evaluations + run_dir: the directory to save results to + lower_bounds: the lower bound of the search space + upper_bounds: the upper_bound of the search space + """ + super().__init__(run_dir) + + self.run_dir = run_dir + + lower_bounds = np.array(lower_bounds, dtype=np.float32) + upper_bounds = np.array(upper_bounds, dtype=np.float32) + + assert type(max_evals) == int + assert type(input_dim) == int + assert len(lower_bounds) == len(upper_bounds) + assert len(lower_bounds) == input_dim + + self.function = function + self.max_evals = max_evals + self.input_dim = input_dim + self.lower_bounds = lower_bounds + self.upper_bounds = upper_bounds + + def optimize(self) -> None: + """ + Run the optimization. + + Returns: None + + """ + assert not self._optimized + + points = np.random.uniform(self.lower_bounds, self.upper_bounds, (self.max_evals, self.input_dim)) + try: + ys = np.array(self.function(points)) + except: + warning("Could not run function on all points at once even though" + " the function should support this.") + ys = np.array([self.function(y) for y in points]) + + self.ys = ys + self._optimized = True + + def optimization_results_raw( + self, + ) -> Tuple[Optional[np.ndarray], np.ndarray]: + assert self._optimized, "Model hasn't been optimized yet" + return None, self.ys diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/real_world_benchmarks.py b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/real_world_benchmarks.py new file mode 100644 index 0000000..7286729 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/real_world_benchmarks.py @@ -0,0 +1,732 @@ +import os +import stat +import subprocess +import sys +import tempfile +import urllib +from logging import info, warning +from platform import machine +from typing import Union, List, Optional + +import numpy as np +import pandas as pd +from sklearn.preprocessing import MinMaxScaler +from sklearn.svm import SVR + +from baxus.benchmarks import SyntheticBenchmark, EffectiveDimBenchmark + + +class MoptaSoftConstraints(SyntheticBenchmark): + """ + Mopta08 benchmark with soft constraints as described in https://arxiv.org/pdf/2103.00349.pdf + Supports i386, x86_84, armv7l + + Args: + temp_dir: Optional[str]: directory to which to write the input and output files (if not specified, a temporary directory will be created automatically) + binary_path: Optional[str]: path to the binary, if not specified, the default path will be used + """ + + def __init__( + self, + temp_dir: Optional[str] = None, + binary_path: Optional[str] = None, + noise_std: Optional[float] = 0, + **kwargs, + ): + super().__init__(124, np.ones(124), np.zeros(124), noise_std=noise_std) + if binary_path is None: + self.sysarch = 64 if sys.maxsize > 2 ** 32 else 32 + self.machine = machine().lower() + if self.machine == "armv7l": + assert self.sysarch == 32, "Not supported" + self._mopta_exectutable = "mopta08_armhf.bin" + elif self.machine == "x86_64": + assert self.sysarch == 64, "Not supported" + self._mopta_exectutable = "mopta08_elf64.bin" + elif self.machine == "i386": + assert self.sysarch == 32, "Not supported" + self._mopta_exectutable = "mopta08_elf32.bin" + elif self.machine == "amd64": + assert self.sysarch == 64, "Not supported" + self._mopta_exectutable = "mopta08_amd64.exe" + else: + raise RuntimeError("Machine with this architecture is not supported") + self._mopta_exectutable = os.path.join( + os.getcwd(), "baxus", "benchmarks", "mopta08", self._mopta_exectutable + ) + + if not os.path.exists(self._mopta_exectutable): + basename = os.path.basename(self._mopta_exectutable) + info(f"Mopta08 executable for this architecture not locally available. Downloading '{basename}'...") + urllib.request.urlretrieve( + f"https://mopta.papenmeier.io/{os.path.basename(self._mopta_exectutable)}", + self._mopta_exectutable) + os.chmod(self._mopta_exectutable, stat.S_IXUSR) + + else: + self._mopta_exectutable = binary_path + if temp_dir is None: + self.directory_file_descriptor = tempfile.TemporaryDirectory() + self.directory_name = self.directory_file_descriptor.name + else: + if not os.path.exists(temp_dir): + warning(f"Given directory '{temp_dir}' does not exist. Creating...") + os.mkdir(temp_dir) + self.directory_name = temp_dir + + def __call__(self, x): + super(MoptaSoftConstraints, self).__call__(x) + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + # create tmp dir for mopta binary + + vals = np.array([self._call(y) for y in x]).squeeze() + return vals + np.random.normal( + np.zeros_like(vals), np.ones_like(vals) * self.noise_std, vals.shape + ) + + def _call(self, x: np.ndarray): + """ + Evaluate Mopta08 benchmark for one point + + Args: + x: one input configuration + + Returns:value with soft constraints + + """ + assert x.ndim == 1 + # write input to file in dir + with open(os.path.join(self.directory_name, "input.txt"), "w+") as tmp_file: + for _x in x: + tmp_file.write(f"{_x}\n") + # pass directory as working directory to process + popen = subprocess.Popen( + self._mopta_exectutable, + stdout=subprocess.PIPE, + cwd=self.directory_name, + ) + popen.wait() + # read and parse output file + output = ( + open(os.path.join(self.directory_name, "output.txt"), "r") + .read() + .split("\n") + ) + output = [x.strip() for x in output] + output = np.array([float(x) for x in output if len(x) > 0]) + value = output[0] + constraints = output[1:] + # see https://arxiv.org/pdf/2103.00349.pdf E.7 + return value + 10 * np.sum(np.clip(constraints, a_min=0, a_max=None)) + + @property + def optimal_value(self) -> Optional[np.ndarray]: + """ + Return the "optimal" value. + + Returns: + np.ndarray: -200, some guessed optimal value we never beat + + """ + return np.array(-200.0) + + +class LassoLeukemiaBenchmark(EffectiveDimBenchmark): + """ + 7129-D Leukemia benchmark from https://github.com/ksehic/LassoBench + + Args: + noise_std: ignored + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + + from LassoBench import LassoBench + + self._b: LassoBench.RealBenchmark = LassoBench.RealBenchmark( + pick_data="leukemia", mf_opt="discrete_fidelity" + ) + dim = self._b.n_features + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=22, + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + result = np.array(result_list).squeeze() + return result + np.random.normal( + np.zeros_like(result), np.ones_like(result) * self.noise_std, result.shape + ) + + +class LassoBreastCancerBenchmark(EffectiveDimBenchmark): + """ + 10-D breast cancer benchmark from https://github.com/ksehic/LassoBench + + Args: + noise_std: ignored + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + + from LassoBench import LassoBench + + self._b: LassoBench.RealBenchmark = LassoBench.RealBenchmark( + pick_data="breast_cancer", mf_opt="discrete_fidelity" + ) + dim = self._b.n_features + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=3, + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + result = np.array(result_list).squeeze() + return result + np.random.normal( + np.zeros_like(result), np.ones_like(result) * self.noise_std, result.shape + ) + + +class LassoDiabetesBenchmark(EffectiveDimBenchmark): + """ + 8-D diabetes benchmark from https://github.com/ksehic/LassoBench + + Args: + noise_std: ignored + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + + from LassoBench import LassoBench + + self._b: LassoBench.RealBenchmark = LassoBench.RealBenchmark( + pick_data="diabetes", mf_opt="discrete_fidelity" + ) + dim = self._b.n_features + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=5, + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + result = np.array(result_list).squeeze() + return result + np.random.normal( + np.zeros_like(result), np.ones_like(result) * self.noise_std, result.shape + ) + + +class LassoDNABenchmark(EffectiveDimBenchmark): + """ + 180-D DNA benchmark from https://github.com/ksehic/LassoBench + + Args: + noise_std: ignored + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + + from LassoBench import LassoBench + + self._b: LassoBench.RealBenchmark = LassoBench.RealBenchmark( + pick_data="dna", mf_opt="discrete_fidelity" + ) + dim = self._b.n_features + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=43, + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + result = np.array(result_list).squeeze() + return result + np.random.normal( + np.zeros_like(result), np.ones_like(result) * self.noise_std, result.shape + ) + + +class LassoRCV1Benchmark(EffectiveDimBenchmark): + """ + 19 959-D RCV1 benchmark from https://github.com/ksehic/LassoBench + + Args: + noise_std: ignored + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + + from LassoBench import LassoBench + + self._b: LassoBench.RealBenchmark = LassoBench.RealBenchmark( + pick_data="rcv1", mf_opt="discrete_fidelity" + ) + dim = self._b.n_features + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=75, + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + result = np.array(result_list).squeeze() + return result + np.random.normal( + np.zeros_like(result), np.ones_like(result) * self.noise_std, result.shape + ) + + +class LassoSimpleBenchmark(EffectiveDimBenchmark): + """ + 60-D synthetic Lasso simple benchmark from https://github.com/ksehic/LassoBench . + Effective dimensionality: 5% of input dimensionality. + + Args: + noise_std: if > 0: noisy version with fixed SNR, noiseless version otherwise + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + + from LassoBench import LassoBench + + if noise_std > 0: + warning( + f"LassoBenchmark with noise_std {noise_std} chosen. Will use noisy version with snr ratio 10. The exact value of noise_std will be ignored." + ) + self._b: LassoBench.SyntheticBenchmark = LassoBench.SyntheticBenchmark( + pick_bench="synt_simple", noise=noise_std > 0 + ) + dim = self._b.n_features + + self.effective_dims = np.arange(dim)[self._b.w_true != 0] + info(f"function effective dimensions: {self.effective_dims.tolist()}") + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=len(self.effective_dims), + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + return np.array(result_list).squeeze() + + +class LassoMediumBenchmark(EffectiveDimBenchmark): + """ + 100-D synthetic Lasso medium benchmark from https://github.com/ksehic/LassoBench . + Effective dimensionality: 5% of input dimensionality. + + Args: + noise_std: if > 0: noisy version with fixed SNR, noiseless version otherwise + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + from LassoBench import LassoBench + + if noise_std > 0: + warning( + f"LassoBenchmark with noise_std {noise_std} chosen. Will use noisy version with snr ratio 10. The exact value of noise_std will be ignored." + ) + self._b: LassoBench.SyntheticBenchmark = LassoBench.SyntheticBenchmark( + pick_bench="synt_medium", noise=noise_std > 0 + ) + dim = self._b.n_features + + self.effective_dims = np.arange(dim)[self._b.w_true != 0] + info(f"function effective dimensions: {self.effective_dims.tolist()}") + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=len(self.effective_dims), + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + return np.array(result_list).squeeze() + + +class LassoHighBenchmark(EffectiveDimBenchmark): + """ + 300-D synthetic Lasso high benchmark from https://github.com/ksehic/LassoBench . + Effective dimensionality: 5% of input dimensionality. + + Args: + noise_std: if > 0: noisy version with fixed SNR, noiseless version otherwise + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + from LassoBench import LassoBench + + if noise_std > 0: + warning( + f"LassoBenchmark with noise_std {noise_std} chosen. Will use noisy version with snr ratio 10. The exact value of noise_std will be ignored." + ) + self._b: LassoBench.SyntheticBenchmark = LassoBench.SyntheticBenchmark( + pick_bench="synt_high", noise=noise_std > 0 + ) + dim = self._b.n_features + + self.effective_dims = np.arange(dim)[self._b.w_true != 0] + info(f"function effective dimensions: {self.effective_dims.tolist()}") + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=len(self.effective_dims), + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + return np.array(result_list).squeeze() + + +class LassoHardBenchmark(EffectiveDimBenchmark): + """ + 1000-D synthetic Lasso hard benchmark from https://github.com/ksehic/LassoBench . + Effective dimensionality: 5% of input dimensionality. + + Args: + noise_std: if > 0: noisy version with fixed SNR, noiseless version otherwise + **kwargs: + """ + + def __init__(self, noise_std: Optional[float] = 0, **kwargs): + from LassoBench import LassoBench + + if noise_std > 0: + warning( + f"LassoBenchmark with noise_std {noise_std} chosen. Will use noisy version with snr ratio 10. The exact value of noise_std will be ignored." + ) + self._b: LassoBench.SyntheticBenchmark = LassoBench.SyntheticBenchmark( + pick_bench="synt_hard", noise=noise_std > 0 + ) + dim = self._b.n_features + + self.effective_dims = np.arange(dim)[self._b.w_true != 0] + info(f"function effective dimensions: {self.effective_dims.tolist()}") + + super().__init__( + dim=dim, + ub=np.full(dim, fill_value=1.0), + lb=np.full(dim, fill_value=-1.0), + effective_dim=len(self.effective_dims), + noise_std=noise_std, + ) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + x = np.array(x, dtype=np.double) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + result_list = [] + for y in x: + result = self._b.evaluate(y) + result_list.append(result) + return np.array(result_list).squeeze() + + +class SVMBenchmark(SyntheticBenchmark): + def __init__( + self, + data_folder: Optional[str] = None, + noise_std: Optional[float] = 0, + **kwargs, + ): + """ + SVM Benchmark from https://arxiv.org/abs/2103.00349 + + Support also a noisy version where the model is trained on random subset of 250 points + which is used whenever noise_std is greater than 0. + + Args: + data_folder: the folder where the slice_localization_data.csv is located + noise_std: noise standard deviation. Anything greater than 0 will lead to a noisy benchmark + **kwargs: + """ + self.value = np.inf + self.best_config = None + self.noisy = noise_std > 0 + if self.noisy: + warning("Using a noisy version of SVMBenchmark where training happens on a random subset of 250 points." + "However, the exact value of noise_std is ignored.") + super(SVMBenchmark, self).__init__( + 388, lb=np.zeros(388), ub=np.ones(388), noise_std=noise_std + ) + self.X, self.y = self._load_data(data_folder) + if not self.noisy: + np.random.seed(388) + idxs = np.random.choice(np.arange(len(self.X)), min(10000, len(self.X)), replace=False) + half = len(idxs) // 2 + self._X_train = self.X[idxs[:half]] + self._X_test = self.X[idxs[half:]] + self._y_train = self.y[idxs[:half]] + self._y_test = self.y[idxs[half:]] + + def _load_data(self, data_folder: Optional[str] = None): + if data_folder is None: + data_folder = os.path.join(os.getcwd(), "data") + if not os.path.exists(os.path.join(data_folder, "CT_slice_X.npy")): + sld_dir = os.path.join(data_folder, "slice_localization_data.csv.xz") + sld_bn = os.path.basename(sld_dir) + info(f"Slice localization data not locally available. Downloading '{sld_bn}'...") + urllib.request.urlretrieve( + f"http://mopta-executables.s3-website.eu-north-1.amazonaws.com/{sld_bn}", + sld_dir) + data = pd.read_csv( + os.path.join(data_folder, "slice_localization_data.csv.xz") + ).to_numpy() + X = data[:, :385] + y = data[:, -1] + np.save(os.path.join(data_folder, "CT_slice_X.npy"), X) + np.save(os.path.join(data_folder, "CT_slice_y.npy"), y) + X = np.load(os.path.join(data_folder, "CT_slice_X.npy")) + y = np.load(os.path.join(data_folder, "CT_slice_y.npy")) + X = MinMaxScaler().fit_transform(X) + y = MinMaxScaler().fit_transform(y.reshape(-1, 1)).squeeze() + return X, y + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + super(SVMBenchmark, self).__call__(x) + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + x = x ** 2 + + errors = [] + for y in x: + C = 0.01 * (500 ** y[387]) + gamma = 0.1 * (30 ** y[386]) + epsilon = 0.01 * (100 ** y[385]) + length_scales = np.exp(4 * y[:385] - 2) + + svr = SVR(gamma=gamma, epsilon=epsilon, C=C, cache_size=1500, tol=0.001) + if self.noisy: + np.random.seed(None) + idxs = np.random.choice(np.arange(len(self.X)), min(500, len(self.X)), replace=False) + half = len(idxs) // 2 + X_train = self.X[idxs[:half]] + X_test = self.X[idxs[half:]] + y_train = self.y[idxs[:half]] + y_test = self.y[idxs[half:]] + svr.fit(X_train / length_scales, y_train) + pred = svr.predict(X_test / length_scales) + error = np.sqrt(np.mean(np.square(pred - y_test))) + else: + svr.fit(self._X_train / length_scales, self._y_train) + pred = svr.predict(self._X_test / length_scales) + error = np.sqrt(np.mean(np.square(pred - self._y_test))) + + errors.append(error) + if errors[-1] < self.value: + self.best_config = np.log(y) + self.value = errors[-1] + return np.array(errors).squeeze() + + + +class IOH_func_base(SyntheticBenchmark): + """ + The Rastrigin function with many local minima (see https://www.sfu.ca/~ssurjano/rastr.html) + + WARNING: This function has its optimum at the origin. This might give a misleading performance for BAxUS + as the origin will always be reachable irregardless of the embedding. + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + r""" + This is a wrapper which extends the IOH Single Objective defined instances + to work with BAxUS. + """ + + try: + import ioh + except ModuleNotFoundError as e: + print("Install the ioh module as `pip install ioh`") + + def __init__(self, + #f:object, + dim:int, + #lb:int, + #ub:int, + noise_std: Optional[float] = None, + )->None: + + r""" + Args: + -------- + - f: A IOH Real Single Objective instance + - noise_std: Standard deviation of the observation noise. + - negate: If True, negate the function. + """ + self.__dim = dim + + self.__bounds = [(-5, 5) for _ in range(self.__dim)] + + #self.__f:object = f + + # Convert the bounds definition + lb_:np.ndarray = np.ravel(np.array(self.__bounds)[:,0]) + ub_:np.ndarray = np.ravel(np.array(self.__bounds)[:,1]) + + # Use the constructor from `SyntheticBenchmark` + super().__init__(dim= self.__dim,lb=lb_,ub=ub_, + noise_std=noise_std) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]])->np.ndarray: + + r""" + This is the implementation of the `__call__` function for this wrapper class. + + Args: + --------- + - x: A list or array with possible points to evaluate. + """ + # Call the __call__ function from the polymorphic class + + + #x =super().__call__(x) + x:np.ndarray = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + + result = self.__f(x.ravel()) + + return result + + + @property + def dim(self)->int: + return self.__dim + + @property + def bounds(self)->Union[float,List[float]]: + return self.__bounds + + @property + def IOH_instance(self)->ioh.problem.RealSingleObjective: + return self.__f \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/synthetic_benchmark_functions.py b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/synthetic_benchmark_functions.py new file mode 100644 index 0000000..ae17fa4 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/benchmarks/synthetic_benchmark_functions.py @@ -0,0 +1,283 @@ +import math +import os +from typing import Optional + +import numpy as np +import torch +from botorch.test_functions import Ackley as BotorchAckley +from botorch.test_functions import Branin as BotorchBranin +from botorch.test_functions import DixonPrice as BotorchDixonPrice +from botorch.test_functions import Griewank as BotorchGriewank +from botorch.test_functions import Hartmann as BotorchHartmann +from botorch.test_functions import Levy as BotorchLevy +from botorch.test_functions import Michalewicz as BotorchMichalewicz +from botorch.test_functions import Rastrigin as BotorchRastrigin +from botorch.test_functions import Rosenbrock as BotorchRosenbrock + +from baxus.benchmarks import EffectiveDimBoTorchBenchmark + + +class AckleyEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + A benchmark function with many local minima (see https://www.sfu.ca/~ssurjano/ackley.html) + + WARNING: This function has its optimum at the origin. This might give a misleading performance for BAxUS + as the origin will always be reachable irregardless of the embedding. + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__(self, dim=200, noise_std=None, effective_dim: int = 10): + super(AckleyEffectiveDim, self).__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + lb=np.full(shape=effective_dim, fill_value=-32.768), + ub=np.full(shape=effective_dim, fill_value=32.768), + benchmark_func=BotorchAckley, + ) + + +class ShiftedAckley10(EffectiveDimBoTorchBenchmark): + """ + A benchmark function with many local minima (see https://www.sfu.ca/~ssurjano/ackley.html) + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__(self, dim=200, noise_std=None, ): + self.offsets = np.array([-14.15468831, -17.35934204, 4.93227439, 30.68108305, + -20.94097318, -9.68946759, 11.23919487, 4.93101114, + 2.87604112, -31.0805155]) + + super(ShiftedAckley10, self).__init__( + dim=dim, + effective_dim=10, + noise_std=noise_std, + lb=np.full(shape=10, fill_value=-32.768) - self.offsets, + ub=np.full(shape=10, fill_value=32.768) - self.offsets, + benchmark_func=BotorchAckley, + ) + + def __call__(self, x): + x = np.array(x) + return super().__call__(x, self.offsets) + + +class RosenbrockEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + A valley-shape benchmark function (see https://www.sfu.ca/~ssurjano/rosen.html) + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__( + self, dim: int = 200, noise_std: Optional[float] = None, effective_dim: int = 10 + ): + super().__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + ub=np.full(shape=effective_dim, fill_value=10), + lb=np.full(shape=effective_dim, fill_value=-5), + benchmark_func=BotorchRosenbrock, + ) + + +class HartmannEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + A valley-shape benchmark function (see https://www.sfu.ca/~ssurjano/rosen.html) + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__( + self, dim: int = 200, noise_std: Optional[float] = None, effective_dim: int = 6 + ): + assert effective_dim == 6 + super().__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + ub=np.ones(effective_dim), + lb=np.zeros(effective_dim), + benchmark_func=BotorchHartmann, + ) + + +class BraninEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + The Branin function with three local minima (see https://www.sfu.ca/~ssurjano/branin.html) + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__( + self, dim: int = 200, noise_std: Optional[float] = None, effective_dim: int = 2 + ): + assert effective_dim == 2 + super().__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + lb=np.array([-5.0, -5.0]), + ub=np.array([15.0, 15.0]), + benchmark_func=BotorchBranin, + ) + + +class LevyEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + The Levy function with many local minima (see https://www.sfu.ca/~ssurjano/levy.html) + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__(self, dim=200, noise_std=None, effective_dim: int = 2): + super(LevyEffectiveDim, self).__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + lb=np.full(shape=effective_dim, fill_value=-10), + ub=np.full(shape=effective_dim, fill_value=10), + benchmark_func=BotorchLevy, + ) + + +class DixonPriceEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + The valley shaped Dixon-Price function (see https://www.sfu.ca/~ssurjano/dixonpr.html) + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__(self, dim=200, noise_std=None, effective_dim: int = 2): + super(DixonPriceEffectiveDim, self).__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + lb=np.full(shape=effective_dim, fill_value=-10), + ub=np.full(shape=effective_dim, fill_value=10), + benchmark_func=BotorchDixonPrice, + ) + + +class GriewankEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + The Griewank function with many local minima (see https://www.sfu.ca/~ssurjano/griewank.html) + + WARNING: This function has its optimum at the origin. This might give a misleading performance for BAxUS + as the origin will always be reachable irregardless of the embedding. + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__(self, dim=200, noise_std=None, effective_dim: int = 2): + super(GriewankEffectiveDim, self).__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + lb=np.full(shape=effective_dim, fill_value=-600), + ub=np.full(shape=effective_dim, fill_value=600), + benchmark_func=BotorchGriewank, + ) + + +class MichalewiczEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + The Michalewicz function with steep drops (see https://www.sfu.ca/~ssurjano/michal.html) + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__(self, dim=200, noise_std=None, effective_dim: int = 2): + super(MichalewiczEffectiveDim, self).__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + lb=np.full(shape=effective_dim, fill_value=0), + ub=np.full(shape=effective_dim, fill_value=math.pi), + benchmark_func=BotorchMichalewicz, + ) + + +class RastriginEffectiveDim(EffectiveDimBoTorchBenchmark): + """ + The Rastrigin function with many local minima (see https://www.sfu.ca/~ssurjano/rastr.html) + + WARNING: This function has its optimum at the origin. This might give a misleading performance for BAxUS + as the origin will always be reachable irregardless of the embedding. + + Args: + dim: The ambient dimensionality of the function + noise_std: The standard deviation of the noise + effective_dim: The effective dimensionality of the function + """ + + def __init__(self, dim=200, noise_std=None, effective_dim: int = 2): + super(RastriginEffectiveDim, self).__init__( + dim=dim, + effective_dim=effective_dim, + noise_std=noise_std, + lb=np.full(shape=effective_dim, fill_value=-5.12), + ub=np.full(shape=effective_dim, fill_value=5.12), + benchmark_func=BotorchRastrigin, + ) + + + +class RotatedHartmann6(EffectiveDimBoTorchBenchmark): + """ + Version of the rotated Hartmann6 function as described in https://bit.ly/3dZFVXv + + Args: + noise_std: The standard deviation of the noise + """ + + def __init__(self, noise_std: Optional[float] = None, **kwargs): + # bounds taken from https://bit.ly/3e0YgDw + super().__init__(1000, noise_std, 6, np.ones(6), np.zeros(6), BotorchHartmann) + # this is the same matrix as in rotation_matrix_alebo.json + self.rotation_matrix = np.load( + os.path.join("data", "rotation_matrix_alebo.npy") + ) + + def __call__(self, x): + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + else: + assert x.ndim == 2 + x = x.T + x_r = self.rotation_matrix @ x + res = self._benchmark_func.forward(torch.tensor(x_r.T)).numpy().squeeze() + return res diff --git a/mylib/lib_BAxUS/BAxUS/baxus/embeddedturbo.py b/mylib/lib_BAxUS/BAxUS/baxus/embeddedturbo.py new file mode 100644 index 0000000..38ba5a4 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/embeddedturbo.py @@ -0,0 +1,760 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +# Derived from the TuRBO implementation (https://github.com/uber-research/TuRBO) +# Author: anonymous + +import lzma +import math +import os +import sys +import time +from copy import deepcopy +from logging import info, debug, warning +from typing import Tuple, Optional, Any, Union, Dict +from zipfile import ZipFile, ZIP_LZMA + +import gpytorch +import numpy as np +import torch + +from baxus.benchmarks.benchmark_function import Benchmark +from baxus.benchmarks.other_methods import OptimizationMethod +from baxus.gp import train_gp +from baxus.util.acquisition_function_types import AcquisitionFunctionType +from baxus.util.acquisition_functions import ExpectedImprovement +from baxus.util.behaviors import EmbeddedTuRBOBehavior +from baxus.util.behaviors.gp_configuration import GPBehaviour +from baxus.util.projections import AxUS +from baxus.util.space_learning.trust_region import create_Xcand +from baxus.util.utils import ( + one_around_origin_latin_hypercube, + from_1_around_origin, +) + + +class EmbeddedTuRBO(OptimizationMethod): + """ + Embedded TuRBO is the base class for BAxUS. It is the implementation used for our ablation studies and runs + TuRBO in an embedded space. + + Args: + f: the benchmark function + target_dim: the target dimensionality + n_init: the number of initial samples + max_evals: the maximum number of evaluations + behavior: the behavior configuration of the algorithm + gp_behaviour: the behavior of the GP + verbose: whether to print verbose log messages + use_ard: whether to use an ARD kernel + max_cholesky_size: If the size of a LazyTensor is less than max_cholesky_size, then root_decomposition and inv_matmul of LazyTensor will use Cholesky rather than Lanczos/CG. + dtype: the data type to use + run_dir: the directory to write run information to + conf_name: the name of the current configuration + """ + + def __init__( + self, + f: Benchmark, + target_dim: int, + n_init: int, + max_evals: int, + behavior: EmbeddedTuRBOBehavior = EmbeddedTuRBOBehavior(), + gp_behaviour: GPBehaviour = GPBehaviour(), + verbose=True, + use_ard=True, + max_cholesky_size=2000, + dtype="float64", + run_dir: str = ".", + conf_name: Optional[str] = None, + ): + + self.behavior = behavior + super().__init__(conf_name=conf_name, run_dir=run_dir) + # Very basic input checks + + assert max_evals > 0 and isinstance(max_evals, int) + assert n_init > 0 and isinstance(n_init, int) + assert isinstance(verbose, bool) and isinstance(use_ard, bool) + assert max_cholesky_size >= 0 + + assert gp_behaviour.n_mle_training_steps >= 30 and isinstance( + gp_behaviour.n_mle_training_steps, int + ) + assert max_evals > n_init + assert dtype == "float32" or dtype == "float64" + if target_dim > f.dim: + warning( + f"Target dimension {target_dim} is larger than the input dimension {f.dim}. Setting target dimension to input dimension for function {type(f).__name__}." + ) + target_dim = f.dim + + # Save function information + self._target_dim = target_dim + self._input_dim = f.dim + self.f = f + self.lb = f.lb_vec + self.ub = f.ub_vec + + # Settings + + self.n_init = n_init + self.max_evals = max_evals + self.verbose = verbose + self.use_ard = use_ard + self.max_cholesky_size = max_cholesky_size + self.gp_behaviour = gp_behaviour + self.n_evals = 0 + if self._input_dim != self._target_dim: + info(f"eval {self.n_evals}: creating HeSBO embedding for TuRBO instance...") + self.projector = AxUS( + self._input_dim, + self._target_dim, + bin_sizing=self.behavior.embedding_type, + ) + try: + eff_dims = self.f.effective_dims + info( + f"important target dims: {sorted(list(set([self.projector.input_to_target_dim[d] for d in eff_dims])))}" + ) + except: + pass + else: + self.projector = False + + # Hyperparameters + self.mean = np.zeros((0, 1)) + self.signal_var = np.zeros((0, 1)) + self.noise_var = np.zeros((0, 1)) + self.lengthscales = ( + np.zeros((0, self._target_dim)) if self.use_ard else np.zeros((0, 1)) + ) + + # Tolerances and counters + self.succtol = self.behavior.success_tolerance + + # Trust region sizes + self._length_min = self.behavior.min_base_length + self._length_max = self.behavior.max_base_length + self._length_init = self.behavior.initial_base_length + + # Save the full history + self.X = np.zeros((0, self._input_dim)) + self.fX = np.zeros((0, 1)) + + # Device and dtype for GPyTorch + self.dtype = torch.float32 if dtype == "float32" else torch.float64 + + # Initialize parameters + self._restart() + + # History + self._fds = {} + self._model_history_archive = "model_history.zip" + + info(f"Running with the following behavior\n\n{self.behavior.pretty_print()}") + + @property + def failtol(self) -> float: + """ + The fail tolerance of the current trust region. + + Returns: the fail tolerance (=max(4, current target dimensionality)) + + """ + failtol = np.ceil( + np.max( + [ + 4.0, + self._target_dim + ] + ) + ) + return failtol + + @property + def conf_dict(self) -> Dict[str, Any]: + """ + The current behavior configuration as a dictionary + + Returns: the current behavior configuration as a dictionary + + """ + return {**super().conf_dict, **self.behavior.conf_dict} + + @property + def n_cand(self) -> int: + """ + The number of candidates for the discrete Thompson sampling + + Returns: the number of candidates for the discrete Thompson sampling + + """ + return min(100 * self._target_dim, 5000) + + @property + def target_dim(self) -> int: + """ + The target dimensionality. + + Returns: the target dimensionality + + """ + return self._target_dim + + @target_dim.setter + def target_dim(self, target_dim: int) -> None: + """ + Setter for the target dimensionality + + Args: + target_dim: the new target dimensionality + + Returns: + + """ + self._target_dim = target_dim + + @property + def input_dim(self) -> int: + """ + The input dimensionality + + Returns: the input dimensionality + + """ + return self._input_dim + + @input_dim.setter + def input_dim(self, input_dim: int): + """ + Setter for the input dimensionality. + + .. warning:: + Should not be called, throws an error when called. + + Args: + input_dim: the new input dimensionality + + Returns: + + """ + raise AttributeError("Cannot change input dim") + + @property + def length_min(self) -> float: + """ + The minimum base length of the trust region. + + Returns: The minimum base length of the trust region. + + """ + return self._length_min + + @property + def length_max(self) -> float: + """ + The maximum base length of the trust region. + + Returns: The maximum base length of the trust region. + + """ + return self._length_max + + @property + def length_init(self) -> float: + """ + The initial base length of the trust region. + + Returns: The initial base length of the trust region. + + """ + return self._length_init + + def reset(self) -> None: + """ + Reset the state of the current instance (re-initiate the projector, reset global observations, reset local + observations, reset fail- and success counts). Does not reset the target dimensionality + + Returns: None + + """ + self.projector = AxUS( + self._input_dim, self._target_dim, bin_sizing=self.behavior.embedding_type + ) + self.X = np.zeros((0, self._input_dim)) + self.fX = np.zeros((0, 1)) + self.length = self.length_init + if hasattr(self.f, "effective_dims") and isinstance(self.f.effective_dims, np.ndarray): + self._log_property("function_effective_dims", self.f.effective_dims) + self._restart() + + def _resample_and_restart(self, n_points: int, length: float = None) -> None: + """ + Resample new initial points and reset algorithm + + Args: + n_points: number of new points to sample + length: new trust region base length after reset + + Returns: None + + """ + # Initialize parameters + self._restart(length=length) + + # Generate and evaluate initial design points + n_pts = min(self.max_evals - self.n_evals, n_points) + X_init = one_around_origin_latin_hypercube(n_pts, self._target_dim) + + X_init_up = from_1_around_origin( + self.projector.project_up(X_init.T).T, self.lb, self.ub + ) + fX_init = np.array([[self.f(x)] for x in X_init_up]) + # Update budget and set as initial data for this TR + self.n_evals += n_pts + self._X = deepcopy(X_init) + self._fX = deepcopy(fX_init) + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_init_up))) + self.fX = np.vstack((self.fX, deepcopy(fX_init))) + + def _restart(self, length: Optional[float] = None) -> None: + """ + Reset observations, reset counters, reset trust region base length + + Args: + length: new trust region base length after resetting + + Returns: None + + """ + self._X = [] + self._fX = [] + self.failcount = 0 + self.succcount = 0 + if length is None: + self.length = self.length_init + else: + self.length = length + + def _adjust_length(self, fX_next: np.ndarray) -> None: + """ + Adjust the base length of the current trust region depending on the outcome of the next evaluation. + If the next evaluation is better than the current, increase success count and potentially increase TR base length. + Otherwise, increase fail count and potentially decrease TR base length. + + Args: + fX_next: the function value of the next point + + """ + debug( + f"eval {self.n_evals}: failcount = {self.failcount} (failtol = {self.failtol}), " + f"succcount = {self.succcount} (succtol = {self.succtol})" + ) + if np.min(fX_next) < np.min( + self._fX + ) - self.behavior.success_decision_factor * math.fabs(np.min(self._fX)): + debug(f"eval {self.n_evals}: increase success count") + self.succcount += 1 + self.failcount = 0 + else: + debug(f"eval {self.n_evals}: increase failure count") + self.succcount = 0 + self.failcount += 1 + + if self.succcount == self.succtol: # Expand trust region + debug(f"eval {self.n_evals}: expanding trust region") + self.length = min([2.0 * self.length, self.length_max]) + self.succcount = 0 + elif self.failcount == self.failtol: # Shrink trust region + debug(f"eval {self.n_evals}: shrinking trust region") + self.length /= 2.0 + self.failcount = 0 + self._log_property("length_history", f"{self.n_evals}:{self.length}") + + def _create_candidates( + self, + X: np.ndarray, + fX: np.ndarray, + length: float, + gp_behaviour: GPBehaviour, + hypers, + tr_idx: Optional[int] = None, + multiple_lengthscales: bool = False, + ) -> Optional[ + Union[ + Tuple[ + Tuple, + Dict[str, Any], + np.ndarray, + np.ndarray, + ], + Tuple[ + Dict[str, Tuple[np.ndarray, np.ndarray]], Dict[str, Any] + ], + ] + ]: + """ + Generate candidates assuming X has been scaled to [-1,1]^d. + + Args: + X: the local TR data x-values + fX: the local TR data y-values + global_X: the global x-values (used for fitting a PLS if required) + global_y: the global y-values (used for fitting a PLS if required) + length: the current base length + gp_behaviour: the behavior definition of the GP + hypers: the pre-computed GP hyperparameters. If empty, the GP will be trained anew + tr_idx: the trust region index (for TuRBO-m) + multiple_lengthscales: whether to use multiple lengthscales + use_pls: whether to use a PLS kernel + n_pls_components: number of PLS components for PLS kernel + kernel_type: the kernel type of the PLS kernel (only recognized if use_pls is true) + pls: pre-computed PLS. If not given, a new PLS is computed + turbo_1_return_format: whether to use the TuRBO-1 return format (supports multiple acquisition functions) + Returns: + either a tuple (X_candidates, y_candidates, dict of GP hyperparams, PLSContainer, lb of TR, ub of TR) <- TheSBO-1 return format or (dict of best per acquisition function, dict of GP hyperparameters, PLSContainer) + """ + # Pick the center as the point with the smallest function values + # NOTE: This may not be robust to noise, in which case the posterior mean of the GP can be used instead + target_dim = self._target_dim if tr_idx is None else self.target_dims[tr_idx] + + fX = fX.copy() * (-1) + # Standardize local function values. + mu, sigma = np.median(fX), fX.std() + sigma = 1.0 if sigma < 1e-6 else sigma + fX = (deepcopy(fX) - mu) / sigma + + # Figure out what device we are running on + device, dtype = torch.device("cpu"), self.dtype + len_hypers = len(hypers) # save here as overwritten later + # We use CG + Lanczos for training if we have enough data + + with gpytorch.settings.max_cholesky_size(self.max_cholesky_size): + X_torch = torch.tensor(X).to(device=device, dtype=dtype) + y_torch = torch.tensor(fX).to(device=device, dtype=dtype) + + # pass stored pls unless we want to retrain + # Possibly get PLSContainer from GP. If we passed a PLSContainer to train_gp, this is the same one we passed. + # Otherwise, if we passed None and the kernel requires a PLS, it will be a newly trained PLS. + gp, hyper = train_gp( + train_x=X_torch, + train_y=y_torch, + use_ard=self.use_ard, + gp_behaviour=gp_behaviour, + hypers=hypers, + ) + if self.n_evals % 10 == 0 and len_hypers == 0: + # save model + full_arch_path = os.path.join(self.run_dir, self._model_history_archive) + with ZipFile( + full_arch_path, + "a" if os.path.exists(full_arch_path) else "w", + compression=ZIP_LZMA, + ) as zip_archive: + model_path = ( + f"gp_iter_{self.n_evals}.pth" + if tr_idx is None + else f"gp_iter_{self.n_evals}_tr_{tr_idx}.pth" + ) + with zip_archive.open(model_path, "w") as comp_f: + torch.save(gp, comp_f) + + # Create the trust region boundaries + x_center = X[fX.argmax().item(), :][None, :] + # x_center = gp_X[gp_y.argmin().item(), :][None, :] + self._log_property( + "tr_centers" if not multiple_lengthscales else f"tr_{tr_idx}_centers", + f"{self.n_evals}:{x_center.tolist()}", + ) + weights = gp.lengthscales + weights = weights / weights.mean() # This will make the next line more stable + weights = weights / np.prod( + np.power(weights, 1.0 / len(weights)) + ) + + if not multiple_lengthscales: + self.lengthscales = weights + else: + self.lengthscales[tr_idx] = weights + self._log_property( + "lengthscales" + if not multiple_lengthscales + else f"lengthscales_tr_{tr_idx}", + f"{self.n_evals}:{weights.tolist()}", + ) + X_cand, lb, ub = create_Xcand( + x_center=x_center, + weights=weights, + length=length, + dim=target_dim, + n_cand=self.n_cand, + dtype=dtype, + device=device, + ) + + if X_cand.size == 0: + return None + + # Figure out what device we are running on + device, dtype = torch.device("cpu"), self.dtype + + # We may have to move the GP to a new device + gp = gp.to(dtype=dtype, device=device) + + best_per_acq = None + + # We use Lanczos for sampling if we have enough data + with torch.no_grad() if AcquisitionFunctionType.EXPECTED_IMPROVEMENT != self.behavior.acquisition_function else gpytorch.settings.max_cholesky_size( + self.max_cholesky_size): + + if self.behavior.acquisition_function == AcquisitionFunctionType.THOMPSON_SAMPLING: + X_cand_torch = torch.tensor(X_cand).to(device=device, dtype=dtype) + y_cand = ( + gp.likelihood(gp(X_cand_torch)) + .sample(torch.Size([1])) + .t() + .cpu() + .detach() + .numpy() + ) + best_per_acq = (X_cand, (mu + sigma * y_cand) * (-1)) + del X_cand_torch + elif self.behavior.acquisition_function == AcquisitionFunctionType.EXPECTED_IMPROVEMENT: + EI = ExpectedImprovement(gp, best_f=fX.max(), lb=lb, ub=ub) + start = time.time() + X_cand, y_cand = EI.optimize() + end = time.time() + debug( + f"Optimizing EI took {end - start:.2f}s in {self.target_dim} dims with {len(self._X)} datapoints.") + del EI + # y_cand = torch.unsqueeze(y_cand, 1) + + best_per_acq = ( + X_cand, + (mu + sigma * y_cand) * (-1), + ) + + # Remove the torch variables + del X_torch, y_torch, gp + return best_per_acq, hypers, lb, ub + + def _log_property(self, property_name: str, value: Any) -> None: + """ + Log a property to a file. If the file descriptor does not already exist, it is created, otherwise an + already opened file descriptor is used. + + Args: + property_name: the property to log. This will determine the file name + value: the value to log. This is just appended to the file if it already exists. + + Returns: None + + """ + path = os.path.join(self.run_dir, f"{property_name}.txt.xz") + if property_name not in self._fds: + self._fds[property_name] = lzma.open(path, "wt") + self._fds[property_name].write(f"{value}\n") + + def _select_candidates(self, best_per_acq: Tuple[np.ndarray, np.ndarray]) -> Tuple[np.ndarray, np.ndarray]: + """ + Choose the next evaluation point. + + Args: + best_per_acq: Tuple of x-values and acquisition function values of the candidates. + + Returns: The next point according to the acquisition function selected. + + """ + """Select candidates.""" + X_next = np.ones((1, self._target_dim)) + indbests = [] + X_cand, y_cand = best_per_acq + # Pick the best point and make sure we never pick it again + if self.behavior.acquisition_function == AcquisitionFunctionType.THOMPSON_SAMPLING: + indbest = np.argmin(y_cand[:, 0]) + elif self.behavior.acquisition_function == AcquisitionFunctionType.EXPECTED_IMPROVEMENT: + if y_cand.size > 1: + indbest = np.argmax(y_cand[:, 0]) + else: + indbest = 0 + else: + raise RuntimeError("unknown acquisition function type") + indbests.append(indbest) + X_next[0, :] = deepcopy(X_cand[indbest, :]) + del X_cand, y_cand + + return X_next, np.array(indbests).squeeze() + + def _inner_optimization_step(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Create candidates, select candidate, project up point, evaluate point + + Returns: next point in target space, next point in input space, function value of the next point + + """ + # Warp inputs + X = self._X + fX = deepcopy(self._fX).ravel() + + # Create th next batch + is_cands = self._create_candidates( + X, + fX, + length=self.length, + gp_behaviour=self.gp_behaviour, + hypers={}, + ) + best_per_acq, hypers, lb, ub = is_cands + + # select next batch + X_next, _ = self._select_candidates(best_per_acq) + + # Undo the warping + X_next_up = from_1_around_origin( + self.projector.project_up(X_next.T).T if self.projector else X_next, + self.lb, + self.ub, + ) + + # Evaluate batch + fX_next = np.array([[self.f(x)] for x in X_next_up]) + + # Update trust region + self._adjust_length(fX_next) + + # Update budget and append data + self.n_evals += 1 + self._X = np.vstack((self._X, X_next)) + self._fX = np.vstack((self._fX, fX_next)) + + debug( + f"eval {self.n_evals} on {self.f.fun_name}: new point: {fX_next.min():.4} (current global / local best: {self.fX.min():.4}/{self._fX.min():.4})" + ) + if fX_next.min() < self.fX.min(): + n_evals, fbest = self.n_evals, fX_next.min() + info(f"eval {self.n_evals} on {self.f.fun_name}: new best: {fbest:.4}") + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_next_up))) + self.fX = np.vstack((self.fX, deepcopy(fX_next))) + + return X_next, X_next_up, fX_next + + def _optimum_reached(self, tolerance: float = 1e-3) -> bool: + """ + Whether the optimum was reached according to some absolute tolerance value + + Args: + tolerance: the absolute tolerance. If the difference of the best function value to the optimal function value is less than this, return true. False otherwise. + + Returns: True, if the difference of the best function value to the optimal function value is less than the tolerance. False otherwise. + + """ + try: + optimum = np.array(self.f.optimal_value).squeeze() + current_best = np.min(self.fX) + optimum_reached = math.isclose(optimum, current_best, abs_tol=tolerance) + if optimum_reached: + info( + f"Optimum reached within a tolerance of {tolerance}. Stopping early..." + ) + return optimum_reached + except: + return False + + def optimize(self) -> None: + """ + Run the optimization until the maximal number of evaluations or the optimum are reached. + + Returns: None + + """ + + while self.n_evals < self.max_evals and not (self.behavior.noise > 0 or self._optimum_reached()): + if len(self._fX) > 0 and self.verbose: + n_evals, fbest = self.n_evals, self._fX.min() + info(f"eval {self.n_evals}: restarting with fbest = {fbest:.4}") + sys.stdout.flush() + + # Initialize parameters + self._restart() + + # Generate and evaluate initial design points + n_pts = min(self.max_evals - self.n_evals, self.n_init) + X_init = one_around_origin_latin_hypercube(n_pts, self._target_dim) + X_init_up = from_1_around_origin( + self.projector.project_up(X_init.T).T if self.projector else X_init, + self.lb, + self.ub, + ) + fX_init = np.array([[self.f(x)] for x in X_init_up]) + + ####### + print(fX_init) + # Update budget and set as initial data for this TR + self.n_evals += n_pts + self._X = deepcopy(X_init) + self._fX = deepcopy(fX_init) + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_init_up))) + self.fX = np.vstack((self.fX, deepcopy(fX_init))) + + fbest = self._fX.min() + info(f"eval {self.n_evals}: starting from fbest = {fbest:.4}") + + # Thompson sample to get next suggestions + while ( + self.n_evals < self.max_evals + and self.length >= self.length_min + and not self._optimum_reached() + ): + self._inner_optimization_step() + + self._optimized = True + self._log_property("final_target_dim", self.target_dim) + + def _close_fds(self) -> None: + """ + Close any open file handles. + + Returns: None + + """ + for k, v in self._fds.items(): + info(f"Closing file descriptor for '{k}' logger") + v.close() + del self._fds + self._fds = {} + + def __del__(self): + """ + Close any open file handles. + + Returns: None + + """ + self._close_fds() + + def optimization_results_raw( + self, + ) -> Tuple[Optional[np.ndarray], np.ndarray]: + """ + The observations in the input space and their function values. + + Returns: The observations in the input space and their function values. + + """ + assert self._optimized, "Model hasn't been optimized yet" + return self.X, self.fX.squeeze() diff --git a/mylib/lib_BAxUS/BAxUS/baxus/gp.py b/mylib/lib_BAxUS/BAxUS/baxus/gp.py new file mode 100644 index 0000000..d8f0f78 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/gp.py @@ -0,0 +1,230 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +# Derived from the TuRBO implementation (https://github.com/uber-research/TuRBO) +# Author: Leonard Papenmeier + +import math +from typing import Tuple, Dict, Any, List, Callable + +import numpy as np +import torch +from botorch.models import SingleTaskGP +from gpytorch.constraints.constraints import Interval +from gpytorch.distributions import MultivariateNormal +from gpytorch.kernels import ( + MaternKernel, + ScaleKernel, +) +from gpytorch.likelihoods import GaussianLikelihood +from gpytorch.means import ConstantMean +from torch import Tensor + +from baxus.util.behaviors.gp_configuration import MLLEstimation, GPBehaviour +from baxus.util.gp_utils import ( + initializer_factory, + mle_optimization, + latin_hypercube_hp_grid, + pick_best_from_configurations, +) + + +class GP(SingleTaskGP): + """ + Extension of a single class GP for our purposes. + + Args: + train_x: the x-values of the training points + train_y: the function values of the training points + likelihood: the likelihood to use + ard_dims: the number of ARD dimensions + lengthscale_constraint: the constraints for the lengthscales + outputscale_constraint: the constraints for the signal variances + """ + + def __init__( + self, + train_x, + train_y, + likelihood, + ard_dims, + lengthscale_constraint=None, + outputscale_constraint=None, + ): + super(GP, self).__init__( + train_x, + torch.unsqueeze(train_y, 1) if train_y.ndim == 1 else train_y, + likelihood, + ) + self.likelihood = likelihood + self.mean_module = ConstantMean() + + base_kernel = MaternKernel( + lengthscale_constraint=lengthscale_constraint, + ard_num_dims=ard_dims, + nu=2.5, + ) + self.covar_module = ScaleKernel( + base_kernel, outputscale_constraint=outputscale_constraint + ) + + def forward(self, x: Tensor) -> MultivariateNormal: + """ + Call the GP + + Args: + x: points + + Returns: MultivariateNormal distribution + + """ + mean_x = self.mean_module(x) + covar_x = self.covar_module(x) + return MultivariateNormal(mean_x, covar_x) + + @property + def lengthscales(self) -> np.ndarray: + """ + return the lengthscales of the base kernel depending on the kernel type + """ + weights = ( + self.covar_module.base_kernel.lengthscale.cpu().detach().numpy().ravel() + ) + weights = weights / weights.mean() # This will make the next line more stable + weights = weights / np.prod( + np.power(weights, 1.0 / len(weights)) + ) # We now have weights.prod() = 1 + return weights + + +def train_gp( + train_x: Tensor, + train_y: Tensor, + use_ard: bool, + gp_behaviour: GPBehaviour = GPBehaviour(), + hypers=None, +) -> Tuple[GP, Dict[str, Any]]: + """ + Fit a GP where train_x is in [-1, 1]^D + + Args: + train_x: training data + train_y: training data + use_ard: whether to use automatic relevance detection kernel + gp_behaviour: the configuration of the GP + hypers: hyperparameters for the GP, if passed, the GP won't be re-trained + + Returns: + + """ + if hypers is None: + res_hypers = {} + else: + res_hypers = hypers + assert train_x.ndim == 2 + assert train_y.ndim == 1 + assert train_x.shape[0] == train_y.shape[0] + + # Create hyper parameter bounds + noise_constraint = Interval(5e-4, 0.2) + if use_ard: + lengthscale_constraint = Interval(0.005, 10.0) + else: + lengthscale_constraint = Interval( + 0.005, math.sqrt(train_x.shape[1]) + ) # [0.005, sqrt(dim)] + outputscale_constraint = Interval(0.05, 20.0) # TODO + # Create models + likelihood = GaussianLikelihood(noise_constraint=noise_constraint).to( + device=train_x.device, dtype=train_x.dtype + ) + ard_dims = ( + (train_x.shape[1]) if use_ard else None + ) + model = GP( + train_x=train_x, + train_y=train_y, + lengthscale_constraint=lengthscale_constraint, + outputscale_constraint=outputscale_constraint, + likelihood=likelihood, + ard_dims=ard_dims, + ).to(device=train_x.device, dtype=train_x.dtype) + + # Set model to training mode + model.train() + likelihood.train() + + # Initialize an empty hyperparamter "grid" for multistart GD + hyperparameter_grid = {} + # Initialize an empty list of model initializers, used later in multi-start GD + model_initializers: List[Callable[[GP], None]] = [] + + # If we passed an existing hyperparameter configuration for this model, use it + if res_hypers: + hyperparameter_config = lambda m: m.load_state_dict(res_hypers) + model_initializers.append(hyperparameter_config) + model.load_state_dict(res_hypers) + else: + # Otherwise add bounds and default values to hyperparameter grid + hyperparameter_grid["covar_module.outputscale"] = (0.05, 20.0, 1.0) + + hyperparameter_grid["covar_module.base_kernel.lengthscale"] = ( + 0.005, + 10.0, + 0.5, + ) + hyperparameter_grid["likelihood.noise"] = (5e-4, 0.2, 0.005) + samples = latin_hypercube_hp_grid( + hyperparameter_grid, gp_behaviour.n_initial_samples + ) + + # convert hyperparameter priors to initializers + for i in range(gp_behaviour.n_initial_samples): + hyperparameter_config = {} + for k, v in samples.items(): + hyperparameter_config[k] = v[i] + + initializer = initializer_factory(hyperparameter_config) + model_initializers.append(initializer) + if gp_behaviour.mll_estimation == MLLEstimation.LHS_PICK_BEST_START_GD: + model_initializers = pick_best_from_configurations( + initializers=model_initializers, + model=model, + train_x=train_x, + train_y=train_y, + n_best=gp_behaviour.n_best_on_lhs_selection, + ) + + # save the state dicts for multi-start gradient descent + best_loss = np.inf + best_state_dict = None + for i, initializer in enumerate(model_initializers): + state_dict, loss = mle_optimization( + initializer=initializer, + model=model, + num_steps=gp_behaviour.n_mle_training_steps, + train_x=train_x, + train_y=train_y, + ) + if loss < best_loss: + best_state_dict = state_dict + best_loss = loss + else: + del state_dict + + model.load_state_dict(best_state_dict) + res_hypers = best_state_dict + + # Switch to eval mode + model.eval() + likelihood.eval() + + return model, res_hypers diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__init__.py b/mylib/lib_BAxUS/BAxUS/baxus/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..e17f2f8 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/acquisition_function_types.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/acquisition_function_types.cpython-310.pyc new file mode 100644 index 0000000..01bbb1a Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/acquisition_function_types.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/acquisition_functions.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/acquisition_functions.cpython-310.pyc new file mode 100644 index 0000000..3848a43 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/acquisition_functions.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/data_utils.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/data_utils.cpython-310.pyc new file mode 100644 index 0000000..0dc1466 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/data_utils.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/exceptions.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/exceptions.cpython-310.pyc new file mode 100644 index 0000000..96902fb Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/exceptions.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/gp_utils.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/gp_utils.cpython-310.pyc new file mode 100644 index 0000000..8a70262 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/gp_utils.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/parsing.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/parsing.cpython-310.pyc new file mode 100644 index 0000000..50243d4 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/parsing.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/projections.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/projections.cpython-310.pyc new file mode 100644 index 0000000..5d2310b Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/projections.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/utils.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..114c2bb Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/__pycache__/utils.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/acquisition_function_types.py b/mylib/lib_BAxUS/BAxUS/baxus/util/acquisition_function_types.py new file mode 100644 index 0000000..17c9756 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/acquisition_function_types.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class AcquisitionFunctionType(Enum): + EXPECTED_IMPROVEMENT = 1 + """ + Expected improvement acquisition function. + """ + THOMPSON_SAMPLING = 2 + """ + Thompson sampling acquisition function. + """ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/acquisition_functions.py b/mylib/lib_BAxUS/BAxUS/baxus/util/acquisition_functions.py new file mode 100644 index 0000000..cfe393f --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/acquisition_functions.py @@ -0,0 +1,50 @@ +import math +from typing import Union + +import gpytorch +import numpy as np +import torch +from botorch.acquisition import ExpectedImprovement as _EI +from botorch.optim import optimize_acqf + +from baxus.gp import GP + + +class ExpectedImprovement: + def __init__(self, gp: GP, best_f: Union[float, np.ndarray], lb: np.ndarray, ub: np.ndarray, + evaluation_batch_size: int = 100, ): + self.ub = ub + self.lb = lb + self.evaluation_batch_size = evaluation_batch_size + self.best_f = best_f + self.gp = gp + self._EI = _EI(model=self.gp, best_f=self.best_f) + + def __call__(self, X: np.ndarray): + + def _ei(X): + X = np.expand_dims(X, 1) + return torch.unsqueeze(self._EI(torch.unsqueeze(torch.tensor(X), 1)), 1).detach().numpy() + + if X.ndim == 1: + X = X[np.newaxis, :] + if len(X) > 100: + # batched version + Xs = np.split(X, math.ceil(len(X) / self.evaluation_batch_size)) + eis = [_ei(_X) for _X in Xs] + result = np.concatenate(eis) + else: + result = _ei(X) + return result + + def optimize(self): + with gpytorch.settings.max_cholesky_size(2000): + X_cand, y_cand = optimize_acqf( + acq_function=self._EI, + bounds=torch.tensor([self.lb.reshape(-1), self.ub.reshape(-1)]), + q=1, + num_restarts=20, + raw_samples=100, + options={}, + ) + return X_cand.detach().numpy(), y_cand.detach().numpy() diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__init__.py b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__init__.py new file mode 100644 index 0000000..bd5aee3 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__init__.py @@ -0,0 +1,6 @@ +from baxus.util.behaviors.baxus_configuration import ( + BaxusBehavior, +) +from baxus.util.behaviors.embedded_turbo_configuration import ( + EmbeddedTuRBOBehavior, +) # noqa diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..9a5bb00 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/baxus_configuration.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/baxus_configuration.cpython-310.pyc new file mode 100644 index 0000000..650f73f Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/baxus_configuration.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/embedded_turbo_configuration.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/embedded_turbo_configuration.cpython-310.pyc new file mode 100644 index 0000000..423cfc4 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/embedded_turbo_configuration.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/embedding_configuration.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/embedding_configuration.cpython-310.pyc new file mode 100644 index 0000000..aa17a8b Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/embedding_configuration.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/gp_configuration.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/gp_configuration.cpython-310.pyc new file mode 100644 index 0000000..e30f8f0 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/__pycache__/gp_configuration.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/baxus_configuration.py b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/baxus_configuration.py new file mode 100644 index 0000000..66e8a38 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/baxus_configuration.py @@ -0,0 +1,53 @@ +from dataclasses import dataclass +from typing import Any, Dict + +from baxus.util.behaviors.embedded_turbo_configuration import EmbeddedTuRBOBehavior + + +@dataclass +class BaxusBehavior(EmbeddedTuRBOBehavior): + """ + The behavior of the BAxUS algorithm. + + """ + + n_new_bins: int = 3 + """ + Number of new bins after a splitting. Default: 3 + + """ + + budget_until_input_dim: int = 0 + """ + The budget after which we have reached the input dimension under the assumption that we always fail. + If zero: use the entire evaluation budget. + """ + + adjust_initial_target_dim: bool = True + """ + Whether to adjust the initial target dim such that the final split is as close to the ambient dim as possible. + """ + + def __str__(self): + return ( + f"{super().__str__()}" + f"_nbos_{self.n_new_bins}" + f"_aitd_{self.adjust_initial_target_dim}" + f"_buad_{self.budget_until_input_dim}" + ) + + @property + def conf_dict(self) -> Dict[str, Any]: + """ + The configuration as a dictionary. + + Returns: The configuration as a dictionary. + + """ + base_class_dict = super().conf_dict + this_dict = { + "number of new bins per dimension": self.n_new_bins, + "adjust initial target dimension": self.adjust_initial_target_dim, + "budget until input dimension": self.budget_until_input_dim, + } + return {**base_class_dict, **this_dict} diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/embedded_turbo_configuration.py b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/embedded_turbo_configuration.py new file mode 100644 index 0000000..0017f2b --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/embedded_turbo_configuration.py @@ -0,0 +1,99 @@ +from dataclasses import dataclass +from typing import Any, Dict + +from baxus.util.acquisition_function_types import AcquisitionFunctionType +from baxus.util.behaviors.embedding_configuration import EmbeddingType + + +@dataclass +class EmbeddedTuRBOBehavior: + """ + The behavior of the embedded TuRBO algorithm + + """ + + initial_base_length: float = 0.8 + """ + The initial base side length (see TuRBO paper) + + """ + max_base_length: float = 1.6 + """ + The maximum base side length (see TuRBO paper) + + """ + min_base_length: float = 0.5 ** 7 + """ + The minimum base side length (see TuRBO paper). If you get lower than this, the trust region dies out. + + """ + success_tolerance: int = 3 + """ + The number of times we consecutively have to find a better point in order to expand the trust region, initial value + + """ + acquisition_function: AcquisitionFunctionType = AcquisitionFunctionType.THOMPSON_SAMPLING + """ + The different acquisition functions to use in a multi-batch setting (default: only Thompson sampling) + + """ + noise: float = 0. + """ + The noise of the problem. + """ + + embedding_type: EmbeddingType = EmbeddingType.BAXUS + """ + Uniform bin sizing means that all target bins have approx. equally many contributing input dimensions. + Random bin sizing means that a random target dimension is chosen for each input dimension (standard HeSBO + behavior). + """ + + success_decision_factor: float = 0.001 + """ + The difference wrt to the current incumbent solution required for a next point to be considered a success. + + """ + + def __str__(self): + return ( + f"_linit_{self.initial_base_length}" + f"_lmax_{self.max_base_length}" + f"_lmin_{self.min_base_length}" + f"_successtol_{self.success_tolerance}" + f"_acq_{self.acquisition_function.name}" + f"_noise_{self.noise}" + f"_et_{self.embedding_type.name}" + f"_sdf_{self.success_decision_factor}" + ) + + @property + def conf_dict(self) -> Dict[str, Any]: + """ + The configuration as a dictionary. + + Returns: The configuration as a dictionary. + + """ + return { + "initial base length": self.initial_base_length, + "maximum base length": self.max_base_length, + "minimum base length": self.min_base_length, + "success tolerance": self.success_tolerance, + "acquisition_functions": self.acquisition_function.name, + "observation noise": self.noise, + "embedding type": self.embedding_type.name, + "success decision factor": self.success_decision_factor, + } + + def pretty_print(self) -> str: + """ + A nice string of the configuration. + + Returns: A nice string of the configuration. + + """ + pstring = "" + for k, v in self.conf_dict.items(): + pstring += f"\t-{k}: {v}\n" + return pstring diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/embedding_configuration.py b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/embedding_configuration.py new file mode 100644 index 0000000..54387e1 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/embedding_configuration.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class EmbeddingType(Enum): + BAXUS = 0 + """ + BAxUS embedding where each target bin has approx. the same number of contributing input dimensions. + """ + HESBO = 1 + """ + HeSBO embedding where a target dimension is sampled for each input dimension. + """ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/gp_configuration.py b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/gp_configuration.py new file mode 100644 index 0000000..17d9bbf --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/behaviors/gp_configuration.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass +from enum import Enum + + +class MLLEstimation(Enum): + MULTI_START_GRADIENT_DESCENT = 1 + """ + Sample a number of points and start gradient-based optimization on every point. + """ + LHS_PICK_BEST_START_GD = 2 + """ + Sample a number of points and start gradient-based optimization on the best initial points. + """ + + +@dataclass +class GPBehaviour: + mll_estimation: MLLEstimation = MLLEstimation.LHS_PICK_BEST_START_GD + """ + The maximum-likelihood-estimation method. + """ + n_initial_samples: int = 50 + """ + The initial samples. + """ + n_best_on_lhs_selection: int = 5 + """ + The number of best samples on which to start the gradient-based optimizer. + """ + n_mle_training_steps: int = 50 + """ + The number of gradient updates. + """ + + def __str__(self): + return ( + f"mle_{self.mll_estimation.name}_n_init_mle_{self.n_initial_samples}_" + f"n_best_lhs_{self.n_best_on_lhs_selection}_mle_steps_{self.n_mle_training_steps}" + ) diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/console_entry_point.py b/mylib/lib_BAxUS/BAxUS/baxus/util/console_entry_point.py new file mode 100644 index 0000000..fff7390 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/console_entry_point.py @@ -0,0 +1,7 @@ +import sys + +from baxus.benchmark_runner import main + + +def bench(): + main(sys.argv[1:]) diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/data_utils.py b/mylib/lib_BAxUS/BAxUS/baxus/util/data_utils.py new file mode 100644 index 0000000..ddeed16 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/data_utils.py @@ -0,0 +1,43 @@ +from copy import deepcopy +from typing import Dict, Sequence + +import numpy as np + + +def join_data(X: np.ndarray, dims_and_bins: Dict[int, int]) -> np.ndarray: + """ + After splitting, copy the data from the splitting dim(s) into the new dim(s) + + Args: + X (np.ndarray): the x-values before the splitting + dims_and_bins (Dict[int, int]): the splitting: dims and number of bins. Be warned that we assume an ordered dict + which we are allowed to in newer Python versions. + + Returns: the x-values after splitting + + """ + X = deepcopy(X) + for dim, bin in dims_and_bins.items(): + data_row = X[:, dim] + X = np.hstack((X, np.tile(data_row, bin - 1).reshape(-1, (len(data_row))).T)) + return X + + +def right_pad_sequence(sequence: Sequence[np.ndarray], dtype=np.float64, fill_value: float = 0.0) -> np.ndarray: + """ + Pads a sequence of 1D NumPy arrays to the same length. + + Args: + sequence: sequence of 1D NumPy arrays + dtype: the dtype of the result matrix + fill_value: the value for the padding + + Returns: a matrix of shape (len(sequence), max_sequence_length) where all rows are filled up with fill_value on the right + + """ + max_len = max(len(s) for s in sequence) + padded_matrix = np.full(shape=(len(sequence), max_len), dtype=dtype, fill_value=fill_value) + for i, seq in enumerate(sequence): + assert seq.ndim == 1, "Only 1D arrays are supported" + padded_matrix[i, 0:len(seq)] = seq + return padded_matrix diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/exceptions.py b/mylib/lib_BAxUS/BAxUS/baxus/util/exceptions.py new file mode 100644 index 0000000..c08caef --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/exceptions.py @@ -0,0 +1,30 @@ +class ArgumentError(Exception): + """ + An exception for an illegal input argmument. + """ + pass + + +class EffectiveDimTooLargeException(Exception): + """ + When the effective dimensionality is too large (for example when larger than the input dimensionality). + """ + pass + + +class OutOfBoundsException(Exception): + """ + When a point falls outside the search space. + """ + pass + + +class BoundsMismatchException(Exception): + """ + When the search space bounds don't have the same length. + """ + pass + + +class UnknownBehaviorError(Exception): + pass diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/gp_utils.py b/mylib/lib_BAxUS/BAxUS/baxus/util/gp_utils.py new file mode 100644 index 0000000..1ce3e85 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/gp_utils.py @@ -0,0 +1,127 @@ +from copy import deepcopy +from typing import Dict, Callable, Tuple, List, Optional, OrderedDict + +import numpy as np +import torch +from gpytorch import ExactMarginalLogLikelihood +from scipy.stats import qmc +from torch import Tensor + +from baxus.util.utils import from_unit_cube + + +def pick_best_from_configurations( + initializers: List[Callable[["baxus.gp.GP"], None]], + model: "baxus.gp.GP", + train_x: torch.Tensor, + train_y: torch.Tensor, + n_best: Optional[int] = 1, +) -> List[Callable[["baxus.gp.GP"], None]]: + """ + Pick the n_best best performing initializers from a list of initializers based on a GP and a MLL + Args: + initializers: list of initializers, sets GP hyperparameters + model: the GP model + train_x: the data to evaluate the model likelihood on + train_y: the data to evaluate the model likelihood on + n_best: number of best performing initializers to choose + + Returns: list of initializer functions + + """ + assert n_best <= len(initializers), "At most as many best as we have initializers" + # avoid side effects + model = deepcopy(model) + + losses = [] + for i, initializer in enumerate(initializers): + initializer(model) + model.train() + model.likelihood.train() + mll = ExactMarginalLogLikelihood(model.likelihood, model) + output = model(train_x) + loss = -mll(output, train_y).cpu().detach().numpy() + losses.append(loss) + return np.array(initializers)[np.argsort(losses)[:n_best]].tolist() + + +def mle_optimization( + initializer: Callable[["baxus.gp.GP"], None], + model: "baxus.gp.GP", + num_steps: int, + train_x: torch.Tensor, + train_y: torch.Tensor, +) -> Tuple[OrderedDict[str, Tensor], float]: + """ + Optimize likelihood of a model with an initializer. + :param initializer: the model initializer + :param model: the GP model + :param num_steps: number gradient descent steps + :param kernel_type: the kernel type of the GP model + :param train_x: the training data + :param train_y: the training data + :param mll: the model likelihood + :return: state dict and the average loss + """ + # avoid side effects + model = deepcopy(model) + initializer(model) + model.train() + model.likelihood.train() + mll = ExactMarginalLogLikelihood(model.likelihood, model) # TODO + + optimizer = torch.optim.Adam([{"params": model.parameters()}], lr=0.1) + + # only use half of the optimizer steps if kplsk kernel + cum_loss = 0 + for _ in range( + num_steps + ): + optimizer.zero_grad() + output = model(train_x) + loss = -mll(output, train_y) + cum_loss += loss + loss.backward() + optimizer.step() + return deepcopy(model.state_dict()), cum_loss / num_steps if num_steps > 0 else 0 + + +def initializer_factory( + hyperparameter_configuration: Dict[str, float] +) -> Callable[["turbo.gp.GP"], None]: + """ + Take a hyperparameter configuration and return a lambda initializing a model with this configuration + :param hyperparameter_configuration: the hyperparameter configuration + :return: callabe, defined in GPyTorch model + """ + return lambda m: m.initialize(**hyperparameter_configuration) + + +def latin_hypercube_hp_grid( + hyperparameter_grid: Dict[str, Tuple[float, float, float]], n_samples: int +) -> Dict[str, np.ndarray]: + """ + Draw samples from latin hypercube from hyperparameter grid. Default configuration will always be the first configuration. + :param hyperparameter_grid: dictionary, key: hyperparameter name, value: Tuple[lower_bound, upper_bound, default value] + :param n_samples: number of samples to return, if 1 return default values + :return: dictionary, key: hyperparameter name, value: np.ndarray of sample values (shape: (n_samples, 1)) + """ + return_configs = {} + for k, v in hyperparameter_grid.items(): + return_configs[k] = np.array([v[2]]) + # if only one sample, return the default value + if n_samples == 1: + return return_configs + hp_grid = deepcopy(hyperparameter_grid) + keys = [] + bounds = np.empty((0, 2)) + for k, v in hp_grid.items(): + bounds = np.vstack((bounds, v[:2])) + keys.append(k) + d = len(keys) + sampler = qmc.LatinHypercube(d=d) + sample = sampler.random(n=n_samples - 1) + samples = from_unit_cube(sample, bounds[:, 0], bounds[:, 1]) + for i, k in enumerate(keys): + return_configs[k] = np.hstack((return_configs[k], samples[:, i])) + return return_configs diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/parsing.py b/mylib/lib_BAxUS/BAxUS/baxus/util/parsing.py new file mode 100644 index 0000000..61b92a3 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/parsing.py @@ -0,0 +1,375 @@ +import functools +from argparse import ArgumentParser, Namespace + +from baxus.benchmarks.synthetic_benchmark_functions import ( + RosenbrockEffectiveDim, + BraninEffectiveDim, + HartmannEffectiveDim, + RotatedHartmann6, + AckleyEffectiveDim, + LevyEffectiveDim, + GriewankEffectiveDim, + DixonPriceEffectiveDim, + MichalewiczEffectiveDim, + RastriginEffectiveDim, + ShiftedAckley10, +) +from baxus.util.acquisition_function_types import AcquisitionFunctionType +from baxus.util.behaviors.gp_configuration import MLLEstimation +from baxus.util.behaviors.embedding_configuration import EmbeddingType + + +def parse(args): + """ + Define a CLI parser and parse command line arguments + + Args: + args: command line arguments + + Returns: + Namespace: parsed command line arguments + + """ + parser = ArgumentParser() + required_named = parser.add_argument_group("required named arguments") + parser.add_argument( + "-id", + "--input-dim", + type=int, + default=100, + help="Input dimensionality", + ) + + parser.add_argument( + "-a", + "--algorithm", + type=str, + default="baxus", + choices=["baxus", "embedded_turbo_target_dim", "embedded_turbo_effective_dim", + "embedded_turbo_2_effective_dim", "random_search"], + help="The algorithm" + ) + + parser.add_argument( + "-l", + "--initial-baselength", + type=float, + default=0.8, + help="The initial base length.", + ) + parser.add_argument( + "-lmin", + "--min-baselength", + type=float, + default=0.5 ** 7, + help="The minimum base length.", + ) + parser.add_argument( + "-lmax", + "--max-baselength", + type=float, + default=1.6, + help="The maximum base length.", + ) + + parser.add_argument( + "-td", + "--target-dim", + type=int, + default=10, + help="Target dimensionality", + ) + + parser.add_argument( + "-n", "--n-init", type=int, help="Number of initial sampling points. Default: target dimensionality + 1." + ) + + parser.add_argument( + "-r", + "--num-repetitions", + default=1, + type=int, + help="Number of independent repetitions of each run.", + ) + + parser.add_argument( + "-m", + "--max-evals", + type=int, + default=300, + help="Max number of evaluations of each algorithm.", + ) + parser.add_argument( + "--noise-std", + default=0.0, + type=float, + help="Standard deviation of the noise of the objective function.", + ) + + required_named.add_argument( + "-f", + "--function", + choices=[ + "hartmann6", + "branin2", + "rosenbrock2", + "rosenbrock5", + "rosenbrock10", + "rosenbrock15", + "ackley2", + "shiftedackley10", + "ackley1", + "rosenbrock-domain-fixed", + "levy2", + "levy1", + "levy43", + "dixonprice2", + "griewank2", + "griewank1", + "michalewicz2", + "michalewicz15", + "michalewicz1", + "rastrigin2", + "rastrigin1", + "svm", + "lasso-leukemia", + "lasso-breastcancer", + "lasso-dna", + "lasso-rcv1", + "lasso-diabetes", + "lasso-simple", + "lasso-medium", + "lasso-high", + "lasso-hard", + "mopta08", + "hartmann6in1000_rotated", + "rosenbrock5in1000_rotated", + "ioh_function" + ], + required=True, + ) + + parser.add_argument( + "--results-dir", + type=str, + default="results", + help="Base directory to store results in", + ) + parser.add_argument( + "--run-description", + type=str, + default="", + help="Short description that will be added to the run directory", + ) + parser.add_argument( + "-bins", "--new-bins-on-split", type=int, default=3 + ) + + parser.add_argument( + "--multistart-samples", + help="Number of multistart samples for the MLE GD optimization. Samples will be drawn from " + "latin hypercube (if more than 1, otherwise the default value will be used", + type=int, + default=100, + ) + + parser.add_argument( + "--multistart-after-sample", + type=int, + default=10, + help="Only recognized for '--mle-optimization sample-and-choose-best'. Number of multi-start " + "gradient descent optimization out of the '--multistart-samples best ones.", + ) + + parser.add_argument( + "--mle-optimization", + choices=["multistart-gd", "sample-and-choose-best"], + type=str, + default="sample-and-choose-best", + help="'multistart-gd': sample --multistart-samples different starting points for the hyperparameters and start " + "gradient descent for each of them. 'sample-and-choose-best': evaluate -mss many " + "initial configurations and start ", + ) + parser.add_argument( + "--mle-training-steps", + type=int, + default=50, + help="Number of GD steps in MLE maximization.", + ) + + parser.add_argument( + "--acquisition-function", + type=str, + default="ts", + choices=["ts", "ei"], + help="The acquisition functions to use. Either 'ei' or 'ts'" + ) + + parser.add_argument( + "--embedding-type", + type=str, + choices=["hesbo", "baxus"], + default="baxus", + help="How to choose the bin sizes for the HeSBO embedding. 'hesbo': original HeSBO choice, pick " + "one target dimension for each input dimension at random. 'baxus': ensure (almost) uniform" + " bin sizes.", + ) + + parser.add_argument( + "--budget-until-input-dim", + type=int, + default=0, + help="The evaluation budget after which we reach the input dimension under the assumption that " \ + "we always fail in making progress." + ) + + parser.add_argument( + "-v", "--verbose", action="store_true", help="Whether to print debug messages" + ) + + parser.add_argument( + "--adjust-initial-target-dimension", action="store_true", help="Whether to adjust the initial target dimension" + " such that the final split is as close " + "as possible to the " + "ambient dimension for BAxUS." + ) + + pars = parser.parse_args(args) + + # load required benchmarks + benchmark_loader(pars.function, pars) + return pars + + +acquisition_function_mapper = { + "ts": AcquisitionFunctionType.THOMPSON_SAMPLING, + "ei": AcquisitionFunctionType.EXPECTED_IMPROVEMENT, +} + +mle_optimization_mapper = { + "multistart-gd": MLLEstimation.MULTI_START_GRADIENT_DESCENT, + "sample-and-choose-best": MLLEstimation.LHS_PICK_BEST_START_GD, +} + +embedding_type_mapper = { + "baxus": EmbeddingType.BAXUS, + "hesbo": EmbeddingType.HESBO, +} +_fun_mapper = {} + + +def benchmark_loader(bench: str, args: Namespace): + """ + Import the required implementation of a benchmark. We use this class to avoid imports of benchmarks that require + optional dependencies. + + Args: + bench: the benchmark name + args: the parsed command line arguments + + Returns: + None. Just import the benchmark implementation. + + """ + + if bench == "lasso-leukemia": + from baxus.benchmarks.real_world_benchmarks import LassoLeukemiaBenchmark + + _fun_mapper[bench] = LassoLeukemiaBenchmark + + if bench == "lasso-breastcancer": + from baxus.benchmarks.real_world_benchmarks import LassoBreastCancerBenchmark + + _fun_mapper[bench] = LassoBreastCancerBenchmark + + if bench == "lasso-dna": + from baxus.benchmarks.real_world_benchmarks import LassoDNABenchmark + + _fun_mapper[bench] = LassoDNABenchmark + + if bench == "lasso-diabetes": + from baxus.benchmarks.real_world_benchmarks import LassoDiabetesBenchmark + + _fun_mapper[bench] = LassoDiabetesBenchmark + + if bench == "lasso-rcv1": + from baxus.benchmarks.real_world_benchmarks import LassoRCV1Benchmark + + _fun_mapper[bench] = LassoRCV1Benchmark + if bench == "lasso-simple": + from baxus.benchmarks.real_world_benchmarks import LassoSimpleBenchmark + + _fun_mapper[bench] = LassoSimpleBenchmark + if bench == "lasso-medium": + from baxus.benchmarks.real_world_benchmarks import LassoMediumBenchmark + + _fun_mapper[bench] = LassoMediumBenchmark + if bench == "lasso-high": + from baxus.benchmarks.real_world_benchmarks import LassoHighBenchmark + + _fun_mapper[bench] = LassoHighBenchmark + if bench == "lasso-hard": + from baxus.benchmarks.real_world_benchmarks import LassoHardBenchmark + + _fun_mapper[bench] = LassoHardBenchmark + + if bench == "mopta08": + from baxus.benchmarks.real_world_benchmarks import MoptaSoftConstraints + + _fun_mapper[bench] = MoptaSoftConstraints + + if bench == "svm": + from baxus.benchmarks.real_world_benchmarks import SVMBenchmark + + _fun_mapper[bench] = SVMBenchmark + + if bench == "ioh_function": + from baxus.benchmarks.real_world_benchmarks import IOH_func_base + + _fun_mapper[bench] = IOH_func_base + + +def fun_mapper(): + """ + Map benchmark names to their implementation. + + Returns: + dict: a mapping of benchmark names to their (partially initialized) classes + + """ + return { + **{ + "hartmann6": functools.partial(HartmannEffectiveDim, effective_dim=6), + "branin2": functools.partial(BraninEffectiveDim, effective_dim=2), + "rosenbrock2": functools.partial( + RosenbrockEffectiveDim, effective_dim=2 + ), + "rosenbrock5": functools.partial( + RosenbrockEffectiveDim, effective_dim=5 + ), + "rosenbrock10": functools.partial( + RosenbrockEffectiveDim, effective_dim=10 + ), + "rosenbrock15": functools.partial( + RosenbrockEffectiveDim, effective_dim=15 + ), + "ackley2": functools.partial(AckleyEffectiveDim, effective_dim=2), + "shiftedackley10": ShiftedAckley10, + "ackley1": functools.partial(AckleyEffectiveDim, effective_dim=1), + "levy2": functools.partial(LevyEffectiveDim, effective_dim=2), + "levy43": functools.partial(LevyEffectiveDim, effective_dim=43), + "levy1": functools.partial(LevyEffectiveDim, effective_dim=1), + "dixonprice2": functools.partial(DixonPriceEffectiveDim, effective_dim=2), + "griewank2": functools.partial(GriewankEffectiveDim, effective_dim=2), + "griewank1": functools.partial(GriewankEffectiveDim, effective_dim=1), + "michalewicz2": functools.partial(MichalewiczEffectiveDim, effective_dim=2), + "michalewicz1": functools.partial(MichalewiczEffectiveDim, effective_dim=1), + "michalewicz15": functools.partial( + MichalewiczEffectiveDim, effective_dim=15 + ), + "rastrigin2": functools.partial(RastriginEffectiveDim, effective_dim=2), + "rastrigin1": functools.partial(RastriginEffectiveDim, effective_dim=1), + "hartmann6in1000_rotated": RotatedHartmann6, + }, + **_fun_mapper, + } diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/projections.py b/mylib/lib_BAxUS/BAxUS/baxus/util/projections.py new file mode 100644 index 0000000..5baf20a --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/projections.py @@ -0,0 +1,199 @@ +from abc import ABC +from logging import warning, info, debug +from typing import Optional, Dict, List + +import numpy as np +from numpy.random import RandomState + +from baxus.util.behaviors.embedding_configuration import EmbeddingType +from baxus.util.data_utils import right_pad_sequence +from baxus.util.exceptions import OutOfBoundsException, UnknownBehaviorError + + +class ProjectionModel(ABC): + def project_up(self, Y: np.ndarray) -> np.ndarray: + raise NotImplementedError() + + def project_down(self, X: np.ndarray) -> np.ndarray: + raise NotImplementedError() + + +class IdentityProjector(ProjectionModel): + def __init__(self, lb, ub): + self.lb = lb + self.ub = ub + + def project_up(self, Y: np.ndarray) -> np.ndarray: + return Y + + def project_down(self, X: np.ndarray) -> np.ndarray: + return X + + +class AxUS(ProjectionModel): + """ + AxUS embedding. Also support HeSBO embedding by choosing RANDOM bin sizing + """ + + def __init__( + self, + input_dim: int, + target_dim: int, + seed: Optional[int] = None, + bin_sizing=EmbeddingType.BAXUS, + ): + self.seed = seed + self.target_dim: int = target_dim + self.input_dim: int = input_dim + self.bin_sizing = bin_sizing + self._reset() + + def _reset(self): + """ + Reset the AxUS embedding. Sample a new AxUS embedding. + :return: + """ + + if self.target_dim > self.input_dim: + warning( + "HeSBO: Got a target dim larger than the input dim. Setting target dim to input dim." + ) + self.target_dim = self.input_dim + if self.target_dim == self.input_dim: + info("HeSBO: Target dim = input dim. Using identity mapping.") + self.S = np.eye(self.target_dim) + else: + if self.bin_sizing == EmbeddingType.BAXUS: + debug("Creating BAxUS embedding.") + input_dim_permutation = np.random.permutation(list(range(self.input_dim))) + + input_dim_bins = np.array_split(input_dim_permutation + 1, self.target_dim) + input_dim_bins = right_pad_sequence(input_dim_bins, dtype=int) + + mtrx = np.zeros((self.target_dim, self.input_dim + 1)) + np.put_along_axis(arr=mtrx, indices=input_dim_bins, + values=np.random.choice(np.array([-1, +1]), size=input_dim_bins.shape), axis=1) + self.S = mtrx[:, 1:] + + elif self.bin_sizing == EmbeddingType.HESBO: + debug("Creating HeSBO embedding.") + target_dims = np.random.choice(np.arange(self.target_dim), size=self.input_dim) + mtrx = np.zeros((self.target_dim, self.input_dim)) + np.put_along_axis(arr=mtrx, indices=target_dims.reshape((1, self.input_dim)), + values=np.random.choice(np.array([-1, +1]), size=self.input_dim), axis=0) + self.S = mtrx + else: + raise UnknownBehaviorError( + f"No such HeSBO bin-sizing behavior: {self.bin_sizing}" + ) + + @property + def S_prime(self) -> np.ndarray: + return self.S.T + + @property + def input_to_target_dim(self) -> Dict[int, int]: + """ + Return the target dimension each input dimension is mapped to. + + Returns: the target dimension each input dimension is mapped to. + + """ + return { + D: int(np.nonzero(self.S[:, D])[0]) for D in range(self.input_dim) + } + + @property + def target_to_input_dim(self) -> Dict[int, List[int]]: + """ + Return a list of input dimensions the target dimension maps to. + + Returns: A list of input dimensions the target dimension maps to. + + """ + return { + d: np.nonzero(self.S[d])[0].tolist() for d in range(self.target_dim) + } + + def project_down(self, X: np.ndarray) -> np.ndarray: + """ + Project one or multiple points from the ambient into the target space. + + Args: + X: Points in the ambient space. Shape: [num_points, input_dim] + + Returns: numpy array, shape: [num_points, target_dim] + + """ + X = np.array(X) + assert len(X.shape) <= 2 + assert X.shape[0] == self.input_dim + if not -1 <= X.min() <= X.max() <= 1: + raise OutOfBoundsException() + return self.S @ X + + def project_up(self, Y: np.ndarray) -> np.ndarray: + """ + Project one or multiple points from the target into the ambient space. + + Args: + Y: Points in the target space. Shape: [num_points, target_dim] + + Returns: numpy array, shape: [num_points, input_dim] + + """ + Y = np.array(Y) + assert len(Y.shape) <= 2 + assert Y.shape[0] == self.target_dim + if not -1 <= Y.min() <= Y.max() <= 1: + raise OutOfBoundsException() + return self.S_prime @ Y + + def contributing_dimensions(self, target_dimension: int) -> np.ndarray: + """ + Returns the dimensions in the ambient space that contribute to a target dimension. + + Args: + target_dimension: the target dimension for which to return the contributing input dimensions + + Returns: the input dimensions contributing to the target dimension + + """ + + return np.nonzero(self.S[target_dimension])[0] + + def increase_target_dimensionality(self, dims_and_bins: Dict[int, int]): + """ + Split up one target dimension. The contributing input dimensions will be randomly assigned to two bins. + One bin is the current target dimension, the other bin will be assigned to a new target dimension. + Therefore, the target dimensionality will be increased by one. The projection matrix will change by this! + The affected target dimension and the new dimension will only have half the number of contributing input + dimensions than the target dimension prior to the splitting. + + Args: + dims_and_bins: the dimensions and the number of bins to split them into + + Returns: Nothing, S_prime gets updated + + """ + + for splitting_target_dim, n_new_bins in dims_and_bins.items(): + contributing_input_dims = np.random.permutation(self.contributing_dimensions(splitting_target_dim)) + non_zero_elements = self.S[splitting_target_dim, contributing_input_dims].squeeze() + + assert len(contributing_input_dims) >= dims_and_bins[splitting_target_dim], ( + f"Only {len(contributing_input_dims)} contributing input dimensions but want to split " + f"into {dims_and_bins[splitting_target_dim]} new bins" + ) + self.target_dim += n_new_bins - 1 # one bin is in the current dim + new_bins = np.array_split(contributing_input_dims + 1, n_new_bins)[1:] + elements_to_move = np.array_split(non_zero_elements, n_new_bins)[1:] + + new_bins_padded = right_pad_sequence(new_bins, dtype=int) + elements_to_move_padded = right_pad_sequence(elements_to_move) + + S_stack = np.zeros((n_new_bins - 1, self.S.shape[1] + 1)) + np.put_along_axis(arr=S_stack, indices=new_bins_padded, values=elements_to_move_padded, axis=1) + self.S[splitting_target_dim, np.hstack(new_bins) - 1] = 0 + + self.S = np.vstack((self.S, S_stack[:, 1:])) diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__init__.py b/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..ec741cb Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__pycache__/trust_region.cpython-310.pyc b/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__pycache__/trust_region.cpython-310.pyc new file mode 100644 index 0000000..a6c4f5d Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/__pycache__/trust_region.cpython-310.pyc differ diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/trust_region.py b/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/trust_region.py new file mode 100644 index 0000000..a0e2ff9 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/space_learning/trust_region.py @@ -0,0 +1,47 @@ +from logging import debug +from typing import Tuple + +import numpy as np +from torch.quasirandom import SobolEngine + + +def create_Xcand( + x_center: np.ndarray, + weights: np.ndarray, + length: float, + dim: int, + n_cand: int, + dtype, + device, +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + + :param x_center: the TR center + :param weights: the weights of the dims + :param length: baselength + :param dim: the target dim + :param n_cand: number of candidates + :param dtype: the data type + :param device: the device + :return: triple, X_cand, lb, ub + """ + debug(f"creating {n_cand} candidates") + lb = np.clip(x_center - weights * length, -1.0, 1.0) + ub = np.clip(x_center + weights * length, -1.0, 1.0) + + # Draw a Sobolev sequence in [lb, ub] + seed = np.random.randint(int(1e6)) + sobol = SobolEngine(dim, scramble=True, seed=seed) + pert = sobol.draw(n_cand).to(dtype=dtype, device=device).cpu().detach().numpy() + pert = lb + (ub - lb) * pert + + # Create a perturbation mask + prob_perturb = min(20.0 / dim, 1.0) + mask = np.random.rand(n_cand, dim) <= prob_perturb + ind = np.where(np.sum(mask, axis=1) == 0)[0] + mask[ind, np.random.randint(0, dim - 1, size=len(ind))] = 1 + + # Create candidate points + X_cand = x_center.copy() * np.ones((n_cand, dim)) + X_cand[mask] = pert[mask] + return X_cand, lb, ub diff --git a/mylib/lib_BAxUS/BAxUS/baxus/util/utils.py b/mylib/lib_BAxUS/BAxUS/baxus/util/utils.py new file mode 100644 index 0000000..50ef1ed --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/baxus/util/utils.py @@ -0,0 +1,232 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +# Derived from the TuRBO implementation (https://github.com/uber-research/TuRBO) +# Author: anonymous + +import argparse +from logging import warning + +try: + from collections.abc import Iterator +except ImportError as e: + warning("Failed to import Iterator from collections.abc. Python < 3.10 won't be supported in the future.") + from collections import Iterator + +try: + from reprlib import repr +except ImportError: + pass +import functools + +import numpy as np +import seaborn as sns + + +def to_unit_cube(x: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray) -> np.ndarray: + """ + Project to [0, 1]^d from hypercube with bounds lb and ub + + Args: + x: the points to scale + lower_bounds: the lower bounds in the unscaled space + upper_bounds: the upper bounds un the unscaled space + + Returns: scaled points + + """ + assert lower_bounds.ndim == 1 and upper_bounds.ndim == 1 and x.ndim == 2 + xx = (x - lower_bounds) / (upper_bounds - lower_bounds) + return xx + + +def to_1_around_origin(x: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray) -> np.ndarray: + """ + Project to [-1, 1]^d from hypercube with bounds lb and ub + + Args: + x: the points to scale + lower_bounds: the lower bounds in the unscaled space + upper_bounds: the upper bounds un the unscaled space + + Returns: the scaled points. + + """ + assert lower_bounds.ndim == 1 and upper_bounds.ndim == 1 and x.ndim == 2 + x = to_unit_cube(x, lower_bounds, upper_bounds) + xx = x * 2 - 1 + return xx + + +def from_unit_cube(x: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray) -> np.ndarray: + """ + Project points that were scaled to unit cube back to full space. + + Args: + x: the points + lower_bounds: the lower bounds of the full space + upper_bounds: the upper bounds of the full space + + Returns: scaled points + + """ + assert lower_bounds.ndim == 1 and upper_bounds.ndim == 1 and x.ndim == 2 + xx = x * (upper_bounds - lower_bounds) + lower_bounds + return xx + + +def from_1_around_origin(x: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray) -> np.ndarray: + """ + Project points that were scaled to one-around-origin cube back to full space. + + Args: + x: the points + lower_bounds: the lower bounds of the full space + upper_bounds: the upper bounds of the full space + sample_zero: A switch to include to sample the zero vector from LHS + + Returns: scaled points + + """ + xx = (x + 1) / 2 + return from_unit_cube(xx, lower_bounds, upper_bounds) + + +def one_around_origin_latin_hypercube(n_pts: int, dim: int, sample_zero: bool) -> np.ndarray: + """ + Basic Latin hypercube implementation with center perturbation in a one-around-origin cube. + + Args: + n_pts: number of points to sample + dim: dimensionality of the space + sample_zero: sample the zero from the initial/repair LHS + + Returns: the LHS points + + """ + X = latin_hypercube(n_pts=n_pts, dim=dim) + if sample_zero: + X[0,:] = np.ones_like(X[0,:])*0.5 + + return X * 2 - 1 + + +def latin_hypercube(n_pts: int, dim: int) -> np.ndarray: + """ + Basic Latin hypercube implementation with center perturbation. + + Args: + n_pts: number of points to sample + dim: dimensionality of the space + + Returns: the LHS points + + """ + X = np.zeros((n_pts, dim)) + centers = (1.0 + 2.0 * np.arange(0.0, n_pts)) / float(2 * n_pts) + for i in range(dim): # Shuffle the center locations for each dimension. + X[:, i] = centers[np.random.permutation(n_pts)] + + # Add some perturbations within each box + pert = np.random.uniform(-1.0, 1.0, (n_pts, dim)) / float(2 * n_pts) + X += pert + return X + + +def str2bool(value: str) -> bool: + """ + Parse string to boolean or throw error if string has no boolean type. + + Args: + value: the string to parse + + Returns: True, if string is truthy, false if string is falsy + + """ + if isinstance(value, bool): + return value + if value.lower() in ("yes", "true", "t", "y", "1"): + return True + elif value.lower() in ("no", "false", "f", "n", "0"): + return False + else: + raise argparse.ArgumentTypeError("Boolean value expected.") + + +class ColorIterator(Iterator): + """ + A color iterator + """ + colors = sns.color_palette("husl", 23) + + def __init__(self): + self.cc = 0 + + def __iter__(self): + return self + + def __next__(self): + c = self.colors[self.cc % 23] + self.cc += 1 + return c + + +def in_range(x: np.ndarray, incumbent: np.ndarray, lb: np.ndarray, ub: np.ndarray): + """ + Whether the point x is within the range of the next slower trust region around incumbent + given the current bounds lb, ub + + Args: + x: the point to test + incumbent: the point to center the next smallest TR around + lb: lower bound of the current trust region + ub: upper bound of the current trust region + + Returns: true if point would fall in the next smaller trust region, false otherwise + + """ + offsets = [(ub.squeeze()[i] - lb.squeeze()[i]) / 4 for i in range(len(incumbent))] + return all( + incumbent[i] - offsets[i] < x[i] < incumbent[i] + offsets[i] + for i in range(len(incumbent)) + ) + + +def star_string(wrap_string: str) -> str: + """ + Wrap string in stars. + + Args: + wrap_string: string to wrap + + Returns: wrapped string + + """ + return f"{''.join(['*'] * (len(wrap_string) + 4))}\n* {wrap_string} *\n{''.join(['*'] * (len(wrap_string) + 4))}" + + +def partialclass(cls, *args, **kwargs): + """ + A partially initialized class + + Args: + cls: the base class + *args: + **kwargs: + + Returns: + + """ + + class PartialClass(cls): + __init__ = functools.partial(cls.__init__, *args, **kwargs) + + return PartialClass \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/baxus_figure.png b/mylib/lib_BAxUS/BAxUS/baxus_figure.png new file mode 100644 index 0000000..a8a2768 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/baxus_figure.png differ diff --git a/mylib/lib_BAxUS/BAxUS/benchmark_runner.py b/mylib/lib_BAxUS/BAxUS/benchmark_runner.py new file mode 100644 index 0000000..d8c7c5f --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/benchmark_runner.py @@ -0,0 +1,6 @@ +import sys + +from baxus.benchmark_runner import main + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/mylib/lib_BAxUS/BAxUS/data/rotation_matrix_alebo.json b/mylib/lib_BAxUS/BAxUS/data/rotation_matrix_alebo.json new file mode 100644 index 0000000..c5add6f --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/data/rotation_matrix_alebo.json @@ -0,0 +1 @@ +[[0.02544996682934575, 0.009574537166452495, -0.058103127520602524, 0.028667861413367236, -0.0355217681935252, -0.016604316880075937, 0.02457914631200122, 0.06831660262365145, -0.0006238226399938474, 0.072518165501472, 0.033466069225168434, -0.011527200257611864, -0.07530578702380952, 0.014178398798682984, -0.03430446276541374, -0.01066757306503117, 0.015042973622327243, -0.03339916565020489, 0.015429404051627184, 0.06137910415120743, -0.04230220270576482, -0.03366326546736503, 0.04999726697382792, 0.017515610672236056, -0.022652699147739003, -0.021162346694844004, 0.009161717920765429, -0.02540990178301067, 0.05279104071502312, -0.005285776582363403, -0.022936330489805628, -0.03753685138706834, -0.02254932230652342, -0.02835962369410157, 0.005944892422419754, -0.00454828556040731, 0.0010128135789340496, -0.030193983491232878, -0.0475732145557355, 0.03513030705165126, 0.01196908634958705, 0.0037014232400676417, -0.0018846905221955104, 0.07039962184403559, 0.015784259629399982, -0.0299971700219201, -0.020147575294624083, -0.035339057808230635, -0.015846595326207503, -0.008777004211869693, 0.040223553196324605, 0.02134320770717852, -0.062358816207578494, -0.028417670799849977, -0.011618130605545536, 0.04415317894754264, -0.01503640406653814, -0.04750027828703065, 0.002525308429364371, 0.002707392955910534, 0.08835118201131428, -0.05350560321514318, -0.05201324888656311, -0.012597445999067577, 0.0015193374813761134, -0.01650155133683012, -0.04615551725099004, 0.02977370162101278, -0.024511188949340006, -0.010678093087685442, -0.06616397557007656, -0.013393626036625344, -0.0008307585586323427, 0.01557292061261327, -0.009816662877438796, -0.00634982811736922, -0.025058932308207396, 0.05603748249887647, -0.019045407339781634, -0.054888250154415126, 0.01082673978074529, 0.04827299447840366, 0.009142120877155113, 0.01995227818361848, -0.019427482229898627, -0.001694072556671922, -0.04917739056654921, 0.004234280853664795, 0.02353096722022128, -0.006172457189858234, -0.024173171174897434, 0.05719802031829663, 0.011933469552045321, -0.01787475683227195, 0.024780160135971424, -0.04038254494374412, -0.02943950962551391, 0.055596242119447616, 0.007771656392393609, -0.020640000032250395, 0.02870037952221334, 0.023188234118250106, -0.0018237229609560125, -0.03584176319237006, 0.02775773097259729, -0.023483423692803457, -0.04817392437547226, -0.02158683929701966, 0.01202962730612544, 0.021205374851379105, -0.03953364350013944, 0.028101255233948342, -0.03354659134837273, 0.014868316812619964, 0.0010892902825457224, 0.029379020788161007, -0.006634779658266286, 0.015682274611741764, -0.015557194317197473, 0.014326205387608634, 0.01866742929003277, -0.06465472117789456, 0.014447520982604649, -0.011027284458509002, -0.004491357023586313, 0.03072477574554824, -0.008721285706118775, -0.02752104393937565, -0.03489636319850242, -0.044653993364481726, 0.03715964440756241, -0.032132194177338716, -0.0275649172427052, -0.013237485929765416, 0.02389424674186741, -0.02724701967488443, 0.023462161339039802, -0.012944720783336555, -0.03724425241442513, 0.03600266067419821, -0.018284301389487292, 0.026370900690282847, -0.000678983412101499, -0.10088129981329742, 0.0029239317525212895, 0.05016344034231465, -0.025566293606524543, -0.04426334135712184, -0.0009406722312208958, 0.04730130417010296, 0.03232615436942289, -0.0010074828127931944, 0.01348258434837365, 0.03374678451431559, 0.01957404735368179, 0.00022063026719628537, -0.00017645017989813445, -0.0014518072560848333, 0.042224557661487805, 0.012436097314277608, -0.013239357060148223, 0.021296077180036434, -0.00013392607468563217, 0.03226112271715656, -0.005862978154962229, 0.03459561409791674, -0.027189191789299353, -0.02195864265200352, -0.0004201371467906479, 0.035355509315858666, 0.06425048035092656, -0.06159708405212547, 0.048343019244404754, 0.04152675753985215, -0.0299744350469573, 0.034268429146775095, -0.014461358927494457, 0.046132565047468155, -0.06271149944514903, 0.0015754014218372654, 0.005664662764855548, -0.056377326888553396, -0.03847415326317977, 0.02697118142363545, 0.0063030233374681, 0.021993158288957083, -0.01449765710719618, -0.010403532833918969, 0.014721839168428442, 0.026517664515774073, -0.018903895648546703, 0.0012540756428439121, 0.031008647016355514, 0.024607870808221143, 0.008391016612096385, -0.03382925773124992, 0.020826417353134914, 0.04264555083920006, -0.0012150351114579754, 0.04027894654242726, -0.07227608224195892, 0.0222614153187953, -0.02070962666529088, -0.005348786028611981, 0.09047184562504831, -0.0399443145431695, 0.036681395956834534, 0.011611599095065839, -0.0026783628929257456, 0.030220592720757576, 0.007949211995958182, 0.030430202915896494, -0.008453846438350036, 0.001987430890492973, -0.01538253321443193, -0.01833173437456127, 0.0002709212406512944, 0.008489964098265624, 0.021235817816013122, 0.00084143897059623, 0.04632868692808529, -0.030082103857458804, 0.04301715996079379, -0.03216669622100307, -0.015661427920926334, 0.005267183543879863, -0.00815101747591674, -0.014615278781262286, 0.012143694693683468, -0.003901152581143365, 0.0630926825716529, 0.028437257524049974, 0.07429036517795731, -0.05152679559109312, 0.06166610428840731, 0.05190292889222631, 0.030764217361379444, -0.0662338637230066, -0.014473956709209677, 0.06054230329625967, 0.003484125337583866, -0.004077818397687257, -0.004014036972957696, 0.014225221680064891, -0.00043126791438360284, -0.09193359127072778, -0.06796480988779202, 0.011311941247418612, 0.0011584298417835152, -0.033989266783988004, -0.006726496323191289, 0.009598275678863276, -0.01682852974742208, -0.00788184955680023, 0.02608286935132159, 0.03136064307209446, 0.03453603159271619, 0.027579334442363854, -0.05589683987791795, -0.025393545006302087, -0.006028774824671259, 0.035157512944897266, 0.00045756454743067364, 0.005523585630424437, 0.00619140524968758, 0.011235583093780954, 0.0004101799604958375, -0.03685080678170204, 0.016307923375214476, 0.0223772298877034, -0.032820078945766655, -0.007606992382938688, -0.03993573175679455, -0.0031454071655047365, 0.015319877861119368, -0.004860899027247003, -0.03786681075906258, -0.03961846988667918, 0.044415042760445715, -0.06025592480924845, 0.05294636632228032, 0.03685573720186807, -0.012644689117056367, -0.037169482564545664, 0.0058716735311822345, -0.01911201800170213, -0.024604709302551736, 0.05568268972436768, -0.061167483619462844, 0.019949070036128363, 0.05863735101769346, -0.016618711118862657, -0.020180135332158337, 0.07978078101974595, 0.0007141436572412401, 0.0037684128991120995, 0.06779251866785406, 0.004288760608178283, -0.02147327302527603, 0.033927791040706784, 0.008573640581554806, -0.027845202384482425, -0.00943393554290449, -0.026105872760025142, 0.007266785167767473, 0.019220751009638565, 0.034249577148739545, 0.01926074993529251, -0.05411880937933463, 0.0028521203267845435, 0.009446840593227243, 0.017635374599867085, -0.04685975913371206, -0.01512077743554284, -0.028315271322213686, 0.0561508089476226, -0.007221160958910436, -0.02948273274685288, 0.0256538410702407, 0.00953646555345523, 0.01530153075507037, -0.003875189669917652, 0.01779558523244992, 0.04051790192266688, -0.0010349754387499067, -0.058761116749689334, 0.0037370013252884253, -0.005723644012324471, -0.06841204116902681, -0.006081952754233316, -0.019651950517148315, -0.02050202592409786, 0.022141818878607954, -0.02735175751011535, -0.024472634934950582, 0.0020688799545815553, -0.03457952612209436, 0.02244266819688071, -0.011169218153425312, -0.025072130421874145, -0.012397192133443341, 0.03357151195505463, -0.006230195604090403, 0.03015190355085056, 0.0008594269159238315, 0.0015361596778206252, 0.10406526915939543, -0.018660699169951913, -0.05318852466012751, -0.021213013122363913, 0.02119468447835584, -0.013516077684948415, 0.012680191334203799, -0.018490005275249938, -0.004409654875366219, -0.01004919966517205, -0.02264029111370543, -0.02962879441855739, -0.04797354099546257, -0.01613220765851406, 0.011391106271089446, 0.0002985882660491209, -0.00717246491925436, -0.008597963373209993, -0.01721532312499551, 0.009678078199187385, -0.03444314400399684, -0.0006110870081299184, 0.030095401285819307, -0.03242552577159918, -0.01996966410454913, -0.023110393835922085, 0.05977582250907947, -0.03200349116009199, -0.0432301821818922, 0.02509733022365558, 0.012685983760961849, 0.0033298739159166313, -0.018162974018985794, 0.012223009496134545, -0.006376756070253344, 0.024613355935691767, 0.009618426960158603, -0.02253354781745811, 0.019726377739358973, 0.029198265910964687, 0.003081121937156081, -0.020998893754629072, -0.017515017172647692, -0.02841567529937914, -0.04210410519210633, 0.005365385102786315, 0.017844878567949484, 0.0028280731509617713, -0.009539481290206889, 0.015682459552584346, -0.0279800756304544, 0.0034367055271386764, -0.0015662483504134544, -0.07635430569220523, -0.015828498468888088, -0.03707116307115235, 0.027489394760517854, 0.07760900715881079, 0.05960165570909173, -0.00012393858597745154, 0.03533043593943542, -0.010513470852040924, 0.05433849155697967, -0.013160155217196021, -0.044175546354151586, 0.026667907802597122, -0.00624258018338364, 0.04396661073197066, 0.043204805856817574, 0.004500639309502591, -0.015718736468537775, 0.021112306975209508, 0.04665864145156651, -0.03738537408123857, -0.011149390048931505, 0.06470137398090567, 0.0008447778802526712, 0.024318258129175117, 0.022167521064307222, 0.014691686904840693, 0.034460186236710114, -0.009550929369839938, 0.012141062580959372, -0.028190884152881727, 0.015643067224705123, 0.007653572411026542, -0.0055859378535763875, 0.03337823835523605, 0.05468522234744894, -0.017562407720009103, 0.06467042526395275, 0.012674141170101922, 0.03225720035256546, -0.015676009708682503, -0.005036417801341434, 0.0682021244027462, 0.06031095261426205, -0.0018923092377590729, 0.00017149838088330836, -0.013912141829860577, 0.052123498161137344, -0.02219699933405572, -0.0673808656854221, -0.005455667235310066, -0.01350980778434929, -0.004079998193585963, 0.010104195775541907, 0.04858984919127531, -0.011555670550317598, -0.05683992369931614, 0.04219093882017361, -0.011768017064248518, -0.014830933335194914, 0.06335728663991559, 0.03026019936293646, -0.03225621143316863, -0.008941308126161174, -0.025749856735587218, -0.006423390521150042, -0.04240160749535543, -0.02534452817047598, -0.0923626333954701, -0.06332636638363964, -0.004340713859562943, -0.028358347662619125, -0.021329461684091958, 0.001338488829787546, -0.012095086399903632, 0.0524100829458531, -0.05618674875785418, -0.02933754752513129, -0.02082479880704903, 0.0061017450584318, 0.04758180371689842, -0.02562768229923555, 0.04254254618215783, 0.005598302215425604, 0.001597504765323405, 0.011499204708492935, -0.010038670470154162, -0.011364971907213684, 0.05371746009331105, -0.012674742722117823, -0.003478943756488481, -0.016420953557072297, -0.008789669107980534, 0.008579086033650332, 0.0008804650281343512, -0.02752188304205001, -0.01267994501219349, 0.0036103785243878136, 0.004685290762301383, 0.008670226785288775, -0.00699971720382852, -0.025543894708458754, 0.02070346897282023, -0.05155037733109825, -0.03463072193702552, -0.028377802594538523, 0.003062088388507453, -0.007377151397203944, -0.048199015353242364, 0.03058795120377042, -0.008937922369892578, -0.039467696391554216, 0.008602301969437828, 0.029752008811578263, -0.0063994793183276425, -0.011005349644175183, -0.0015634208593509006, -0.02078278956498697, -0.011300471062667457, 0.0299582083977062, 0.007959610884624232, 0.0005730264526975677, 0.001763169752962604, -0.049507934074479384, -0.003693872202712282, 0.006372525798574993, -0.0022043018152753885, -0.04527486918657121, -0.03132478786798592, 0.0010491703350776582, 0.008713200007881316, 0.036618894058383686, 0.014456866928522435, 0.06315198817283738, -0.012368409358895063, 0.009981281893570861, 0.037575330447387936, 0.03129548350121423, 0.005928451862367609, 0.032474460769431904, 0.0015358371867804934, -0.03451977335105108, -0.01999708647682553, 0.028680358754644766, -0.002322045886485012, -0.08311719561226522, -0.041398332583280156, -0.0066884821572702775, -0.015006733786730495, 0.005293511442263052, -0.037354230788483975, 0.017908849882217106, 0.022608150083536647, 0.027551274535621208, 0.04964016890391346, 0.027959060166013714, -0.04179108383433412, 0.006338596753257193, -0.04935974055515581, -0.006982584317027455, 0.00021566627433725755, -0.018535727367743223, -0.013786677865804842, -0.035745211059006424, -0.054118826242401996, 0.00856375003388984, 0.0376616411709326, -0.030823276372279683, 0.05632439095220414, 0.005983443118483419, -0.06606315329217609, -0.018217016790740413, 0.024985453920891906, 0.06156258773854721, -0.05010149660959409, -0.011693923261393187, -0.062434486720972696, 0.030931985852038713, 0.03858420708156836, 0.005466507280807955, -0.008618499897791246, -0.03940561919713775, 0.018848903515659193, -0.04795199380877849, -0.03616219010325521, -0.025393486311775734, -0.0005316629419990955, -0.023936926861690445, 0.006236724817455975, -0.041402753010796864, 0.02589610307094204, 0.0037782679479585954, -0.07645008422587357, -0.001939292934583975, 0.05315644227581925, 0.00032512489809113747, 0.011060716018612193, -0.03420672543470896, 0.04774112817478683, -0.03720715487521094, 0.047321660923205236, -0.03051656088228274, 0.043359171410497, -0.040995780454288186, -0.03193136855243585, -0.004159204540012843, 0.01407008835128247, 0.027705775432171537, 0.037209257569873595, 0.02551170378367514, 0.013465296308026797, -1.5429144449680045e-05, 0.02194544883402673, 0.010588526981456101, 0.046857993823547384, 0.017829278524572424, -0.015469866331317318, 0.01151855579071382, -0.05297917872170339, 0.004964981704141709, -0.020661179599578146, -0.002451777532827503, -0.042536495852133205, -0.007755170828492804, 0.021210681255928177, -0.014197223408240313, 0.024837588926117482, 0.0278280153233007, 0.0011475715809504614, 0.043037339224761786, -0.00780499205192544, -0.02518458988341412, -0.014135788940900939, -0.04444124318276995, -0.054016536546300634, -0.034099302115737404, 0.02917150435139308, -0.05308183612782863, -0.05165618291529786, -0.03432716693225907, 0.029137017354082716, 0.02449058881720085, 0.031346566795035884, 0.007216395252047302, 0.027140348246400516, 0.010233502033607057, 0.0038677386392154084, 0.023920011603450315, 0.059812861148561575, 0.0022147667516118213, 0.06417614431102979, -0.006144109984115213, 0.051890734226751584, -0.019381656792883124, -0.014444442099476491, 0.0341760628179983, -0.01184829316834197, 0.007969839819992735, 0.024203640602674742, 0.01350193564907138, 0.06774842139795823, 0.016760076063503132, -0.01455876574563842, -0.044597409774678, -0.005965278289559568, -0.0106110116371154, 0.018816205394308567, -0.02389466202680981, -0.09304042695575718, -0.026195693071275693, -0.003328650993743347, 0.05027908367279303, -0.025586400794513225, -0.0716993971399754, -0.015137443374897669, 0.005799374499022873, 0.013483326076409738, -0.05036831912654175, -0.026879964569960342, -0.07200206289503315, -0.009291529746272661, -0.00032708447431179996, 0.002634613806198758, -0.026720160060376287, 0.014831866725950004, -0.04131301331143406, -0.05673920131505779, -0.02472208405983444, 0.05879355672863055, -0.0004385526490997744, 0.01230359156432597, -0.025328946432114977, 0.0063052028630976135, -0.03333120108391044, 0.018684341891596942, 0.030630289738284928, 0.013827144282346854, 0.014515895816816199, -0.006650750373944518, -0.011870937567686268, -0.02165599507640621, -0.010455976354767915, 0.02791836088136045, -0.023224491423490375, 0.05122649891946596, -0.002841847674184444, -0.016234032164947138, -0.002319513173250188, -0.044956921980588885, -0.05131697543813063, 0.010006293278361614, 0.012330385780861947, -0.047490007381838256, 0.03292022103860924, -0.013223841949178153, -0.02136605352847755, -0.0008463221482648744, 0.03479692709916324, 0.04789200848863673, 0.026512841346147967, 0.041402601196805215, 0.03666013572400877, -0.05837997042035416, -0.00035195279929901846, 0.0007421086213932693, 0.009220069320096098, 0.032177415320047795, -0.01690356468468221, 0.0020962195563653755, 0.016133931230476888, 0.0024057915870467845, 0.021500539026504624, -0.0013263427694096123, 0.04520738383059099, 0.0023522286467021446, -0.02962642315572629, -0.07493722114085444, 0.009274243584691762, 0.024096086578617972, -0.014573256289723112, -0.021745561499129287, -0.003837211555728911, -0.003848021424247923, 0.0007196459777235415, -0.027785682264963828, -0.011227855970143199, -0.039486757574886164, -0.021845032500412052, 0.00480013304768488, 0.03297220905726019, -0.015305160724260528, -0.053260203698048715, 0.022744549438974842, 0.019785798190122196, 0.028792996902911372, 0.036559137569331365, 0.015179871751199496, 0.01244257204242631, -0.06564713790555489, -0.01820256439805181, 0.0019438561183134336, 0.01215695536543661, 0.029135685325929646, 0.00012708136607335937, -0.0035887848715945862, 0.00761934485659318, -0.04824257716557489, 0.017190189461099938, 0.018127441979764923, -0.05851166926743259, 0.045925720198745974, 0.0061697016184177845, -0.012038352426429014, 0.02416751240236374, -0.007544980428605972, -0.048898624894895514, -0.007762699026868499, -0.00188917259505334, 0.02334593100770175, -0.06912329269585844, -0.026326385211886343, 0.01942927856610177, 0.032765377860532435, -0.025351448034941514, 0.03548060523713817, 0.07996828152402138, 0.05378759513588525, 0.015304460739872674, 0.004841721580535618, 0.0651579851373533, -0.010845625531765633, 0.017926903143797548, -0.024865194973974606, -0.010018490342399072, -0.051210381794430604, -0.0063228283523982095, 0.03848644502936834, -0.02495817278763482, -0.03554932506689068, 0.0003035828535880386, 0.044720863864625766, -0.07283358592762915, -0.025763557408852732, 0.01804350178126259, -0.04525916878418612, -0.03087292862959327, 0.016890642719804335, 0.006644945960631868, 0.05103176357904997, -0.028690571715064117, -0.02349157602683207, -0.03666406952647047, 0.03933402415437864, 0.03310781725260428, -0.029177198044979293, 0.02702743858169564, -0.0016247041849735265, 0.02749128144548188, 0.021479501506346355, 0.04100449126850208, -0.016177981849709748, 0.04068128498410257, -0.03045361843064771, 0.012518377290962099, 0.004502476966229918, -0.03237062421520531, 0.007777584982420256, -0.04572875890295289, 0.0297716078035051, -0.0041862633160941, 0.011284694612290471, 0.03526365025227816, -0.005625952472555742, 0.0047736537646365625, 0.028902440515400796, -0.05173846797726007, -0.02293324547272178, -0.025028241597442893, 0.020359798820343136, 0.018950718038348482, 0.010330481780502799, -0.03676943227620284, -0.009652119319950282, -0.010900381043634944, 0.022593431362578797, -0.018145042998678248, 0.061008771968577256, 0.011758170604351968, -0.029292554489137704, 0.01857733786897081, -0.06483116510515574, 0.028261527375664997, -0.007756430393117088, 0.07320287853479808, -0.0050863048214077455, -0.014081475283452976, 0.02227221555278466, 0.05377276467938246, -0.010039319983905228, 0.024467567746195563, -0.000910921488631018, 0.029232531123948975, -0.020322391402546054, 0.0022959135299324266, -0.006602513525015889, -0.03164254040445227, -0.004822164978820946, -0.007560440493548586, -0.019632439151389607, -0.017903110751186172, 0.014384773155777039, -0.0017743778638947025, -0.013361952121339846, -0.08835207573184659, 0.03940216953637207, -0.008385204723116458, -0.09198794622850377, 0.013596144095023787, -0.020331473766418473, -0.036927223221769626, -0.05930635360532665, -0.02779553133179956, -0.0048192480147144015, 0.068973613478601, -0.032453960085565815, -0.008494110253831138, -0.005981967974233847, 0.05777071002535412, -0.035864174641367194, -0.005483612481219899, -0.0193362129264717, -0.0220803761616696, -0.05342978690575192, 0.004553319238985557, 0.02808219567791317, -0.041349466305242784, 0.009868719338874269, 0.025389821698599886, -0.06198236426971348, 0.014051366268106432, -0.05176941177575887, 0.007520646706317931, -0.022666063550873028, 0.0016471056408659407, -0.022511699642230664, 0.008249153130504285, -0.052261798131774194, -0.0019015238575151952, -0.025383377793598533, -0.0005627063073191417, 0.014250855939723705, -0.002106474248949486, 0.00485566667247458, -0.006997836505382873, 0.01787223673626606, 0.031821525626735264, 0.04392393790759886, 0.01828106875943795, 0.055158666594602036, -0.03155733652814337, 0.021396514592139784, 0.03044927671945577, 0.0437150510251324, 0.03644146741905808, 0.02246549862547271, -0.018897570111653137, -0.017135325176711628, 0.018269457857250784, -0.044794081906037564, -0.0006053316156140833, 0.01419766660930227, -0.03730115300374363, -0.022944296793010962, -0.026994214633824634, 0.02009700622350624, -0.030452249091681254, 0.012803918968967534, 0.012464433476752055, -0.026807914257007195, -0.024705813653216963, 0.08103160257912372, 0.04325008996287298, 0.008220236017197891, 0.06756035601989532, 0.015982696921125937, -0.02706291725605258, -0.03315890754229484, 0.008180541681448493, -0.058748865277956516, 0.0033132050719494838, -0.018553758990914614, -0.03798794951826333, 0.0005694433107625862, 0.013191295369500488, 0.00936781527736304, 0.028783515201507126, -0.020486772281918822, -0.014910762751660167, -0.02034652202567177, 0.024119387935737244, 0.008475764122883984, 0.00198140262235459, -0.0007643555024034075, -0.04111751291202958, -0.002014509498715809, 0.016146874499167057, -0.0022402919964668202, -0.005154094326828428, 0.022760110203592004, -0.012342958014092416, -0.016736413088264856, -0.0004060514966732276, 0.00029221223678524785, -0.011417054347676578, -0.021808787631605694, 0.031020219993909106, 0.01519779777927508, 0.02213232886408425, -0.02573193140394706, -0.008896541936164416, -0.010508281828083338, -0.03179688736214475, -0.01711000233142099, -0.033199437191908994, 0.009588430258660852, 0.01369707618716735, 0.012986528888189392, 0.018199056524192563, -0.014790595112319333, -0.012491711299531435, -0.004973522996312428, -0.02610381812083688, 0.017878693494987333, 0.022981200865093063, -0.0656841576274024, -0.006330636956291156, -0.03071085635551733, -0.0029391384725188205, -0.007427561513204247, -0.0206718590735725, -0.003123990914104894], [-0.01015303986811999, 0.001030841163751943, -0.027896268238050227, -0.0011696489802250163, -0.0020101945605100445, 0.11856931599098472, 0.022139948623062, 0.02010318930290502, -0.07440098355457364, -0.024334610310945675, -0.01504521988721073, 0.0024305891980669816, 0.005406700083055913, -0.03490746875227754, 0.03472999107678702, 0.0189056541107092, 0.050226018420700506, -0.007528573697391188, 0.014537326896943011, -0.04360058268358098, -0.017213792837196602, -0.01072437051600063, -0.02595787160601739, 0.029576801982920713, 0.020457348401371686, 0.00821186754818479, 0.038779717984996735, 0.009421067322287495, -0.06387534123003841, -0.03722260910894021, -0.01414308697758453, 0.0033720089016298985, 0.015931488033158428, 0.07611052555826757, 0.019082747690929047, -0.01660748196285279, -0.014246525079155262, -0.038513413513461006, -0.03094962770161998, -0.01570520766679024, 0.04489522558640853, 0.0002567895852848413, -0.04174574471840873, 0.06879259625923206, 0.061979001989023404, -0.004849863429026936, -0.01234069149504951, -0.07207391972286795, 0.0034770331712498863, 0.01685530742344266, -0.005134639533506115, -0.023779700000676668, 0.006508402231971401, 0.025607084496068955, 0.0396943832799847, 0.007181697647595088, -0.0007539302310683709, -0.030684398585970524, 0.024726894970491638, 0.05250006381913063, 0.010873496742065614, 0.028953204337900447, -0.018502998633203777, -0.015242660774777076, 0.015247296667607742, -0.010450410598708089, 0.016938347960562865, -0.02569227917706604, -0.007629801890255635, -0.012246135126781224, -0.023498317056226653, -0.026364527594153354, 0.020274173129264716, -0.0034009558857580995, -0.005779235719694523, 0.015160621095489882, -0.013882447226564485, -0.0015927457765849834, 0.03639079159009473, -0.050186904881289685, 0.050355428385891174, -0.010570407234994753, 0.04352567388295576, -0.024643680349360358, -0.012388351983918359, -0.030915305284279212, -0.02654541129387408, -0.010940836334525446, -0.039294264608197016, 0.007691994464038799, 0.02834384329259591, 0.0014505805429562272, 0.024970048225222558, 0.025930407601876787, -0.019779302648881614, 0.006259058738724194, -0.01834301400882257, 0.0030892487288715163, 0.08790852966328686, -0.009494969425186357, 0.03701191438854383, 0.01072055250410467, -0.05387462366025633, -0.02159032557471155, -0.008349815443371688, -0.04090170120125939, 0.051378239226484684, 0.0032491222167340406, -0.02393396130357987, -0.03295694784334419, 0.012281223572794018, 0.00014793828713114202, -0.030208981941263393, -0.017060281574859615, -0.030350878380982316, 0.05738389409759073, 0.03177894772941044, 0.0004082413570973475, -0.013297060457557255, 0.01667702391895596, -0.008367475530735783, 0.009864886776324995, -0.04603567689435302, -0.0008771886533245482, 0.01711053408672908, 0.019368122972228022, 0.01864882327228601, 0.02217572614229736, -0.005411242092449385, -0.0250094744278004, -0.08186124140065852, -0.0420830714697686, -0.060041196206686145, 0.025775521819437795, -0.04103673914682439, 0.004199677006784073, -0.0007699309685163178, -0.021409026024850706, 0.006039382839497312, -0.049684765633317154, 0.0023539936748808443, -0.03760909191396803, -0.028906824164143592, 0.012203641272825923, -0.020506909252057816, 0.009623301470259494, -0.05670506068012822, -0.016108375413587644, -0.04011232295166115, -0.04135667312443743, -0.028768780888598015, 0.02533396853500854, -0.018410725825082987, 0.008460965301370225, 0.06652560795281785, -0.040959934283271555, 0.03394875839002846, -0.006840288626741815, -0.02032339316312763, 0.01970024800324472, -0.0482674270102825, -0.015345294154594456, -0.01200026384138556, -0.0010104617029862394, 0.0046098342301606355, -0.013115875854833738, 0.02231543613213669, 0.012322021747504004, 0.010945991076882775, -0.033551280618276585, 0.00922267530746998, -0.013529248107285435, 0.028167271695776774, -0.03258354852861697, -0.008916674580936411, -0.03319966916181142, -0.012841301642614386, -0.05478832677240397, -0.0042816361271833265, -0.0042754408009425815, -0.018092786421494846, 0.01463542932219392, 0.030788643518782327, -0.016444223762838085, 0.05106940275117795, -0.0271423084934017, -0.012326789061520222, -0.009392655772957134, -0.03865452087076049, 0.021784880401347052, 0.03573001357777589, -0.017813897703711647, 0.02050748892525574, 0.021342604938274383, 0.0032139406621484057, 0.026062365162938472, 0.01664329544076443, -0.016660359601583796, -0.02774321562653661, -0.04101555032436639, 0.039143637877529794, -0.01542282908661605, -0.01662961062159951, -0.05223123918944684, 0.050489092278077435, 0.05359134137128009, -0.0004109275142334546, -0.04395294695428482, 0.015153006890299754, 0.012772555827629569, -0.013967625138660797, 0.026960979886601878, 0.009892612853825512, 0.015947209351902962, 0.004284250659819385, 0.02486826696067346, 0.048796208074554495, 0.05188242094115905, 0.017690472915675912, 0.03554061933465908, -0.044018787209512744, 0.04927736618760717, -0.014917542776583637, 0.010212679580718503, -0.012921330731530845, -0.05045675640197096, 0.005666534725409853, 0.018276787728401613, -0.019581064356578838, 0.0038088368313944666, -0.01587930451559898, -0.04257858105215481, 0.045706034705188414, 0.0323828191513969, -0.002831858845031593, -0.011127040589427756, -0.004110248819159652, -0.06621398382493941, 0.03265892788286002, 0.024556710627969866, 0.016779142706693867, -6.325327385600654e-05, -0.001614099161738474, -0.010117097926408459, 0.1000946071370491, -0.0038411404785507614, -0.02995254698766416, -0.026732268803495787, 0.05167069029807976, -0.021901973298227524, 0.0017803397680634997, -0.013926670731323382, 0.017014259558018326, 0.011809219628670558, -0.06312106317276073, 0.019113411491298367, -0.030457708649848536, 0.01674304877502875, -0.027724717356263535, -0.01202416282116246, 0.03693359929394971, 0.016947080948314444, -0.0225061671018998, -0.008650955622006035, 0.07152007062300192, -0.04393996774311808, 0.01137628474889444, -0.030080163450784292, 3.876641081563626e-05, 0.005504166382827479, -0.03339489669513328, -0.028065109630249864, 0.06117417489761348, 0.03467801510749316, 0.023076026603347004, 0.0008944909678732897, 0.01245468968384475, -0.0019048722690804178, -0.024011992395192312, 0.009762260086930949, -0.008078214601423746, 0.016943144010121616, 0.02718770287853543, 0.009884104351438043, -0.004629974109525276, -0.042943669401976206, 0.054660156191598426, -0.0235049545255522, -0.054454273119515104, 0.07165803220232919, -0.06168281602210213, 0.07346780972684466, -0.006497694518980802, 0.07267241179913564, 0.03244599762099451, -0.02488779431635504, 0.01498243036861498, -0.004627919955770931, -0.016832521233928175, -0.011616428542766156, 0.009734886266631083, -0.044055916726552805, 0.02486251400626638, -0.018742611785652022, 0.01013933031642858, 0.03247559013218589, 0.09459083512993467, -0.0061814596352802505, -0.014654585762934658, 0.0068743989763212845, 0.04296309871796011, -0.008786282849572044, -0.014964814583092335, 0.03715757802916117, 0.029389127468180277, -0.07018910235282234, -0.0022671280623205413, 0.01702884724788028, -0.017284172941825722, 0.0012175066668610995, 0.04875852927184998, 0.04766466836378955, -0.02208446443692416, -0.0055837626816742855, -0.006097394104905557, 0.013027960294989534, 0.024429734394069317, 0.024883868782869533, -0.007717257254975246, -0.03598320922495605, -0.016888323782649102, 0.05484118304136722, -0.007366200362350739, -0.014946114625513662, 0.004642456665663159, -0.010155210627039318, 0.04932915397928191, -0.04401419432867196, 0.03180959620265615, -0.03293197760678114, -0.022434178409792553, -0.028743584627940275, 0.04710926832974503, -0.038305187644416726, 0.004451628705445715, -0.02339983274538951, 0.03305215056312805, -0.015179774895651785, 0.058123001658862, -0.04471535309543776, 0.008274630497605334, 0.04139155060610064, 0.02658140669499307, -0.006459641351841069, -0.03463665666542799, 0.05661594041155597, 0.01988236681056074, -0.0042501222710711675, 0.02970648665182408, -0.05734162638460729, -0.02675330551477144, -0.03563321082571466, -0.0002707623407053085, 0.04510284739174235, -0.013937688461664812, -0.05008320801695838, -0.010644801499488123, 0.05083001174157156, -0.03976794940675296, 0.018139829221634006, -0.061355916446978145, 0.02055241640030546, -0.004017452826107479, -0.01145350088096255, -0.03550841329530561, 0.003463327781951752, 0.015623547385026039, -0.020466679941858332, -0.01164813680138252, -0.025110281479065223, 0.02432365597470158, -0.015583979035415994, 0.03913690220467336, -0.04257360008820674, -0.0293608263563574, -0.0017663957614694203, -0.04741354278251331, 0.00528997822625279, 0.053516330894750094, 0.04895061821796999, -0.053420440027008256, 0.049001916875010904, -0.004338618286564839, -0.03372035456416778, 0.03325273876058476, -0.02069657246263677, -0.042988807817081226, -0.03648131152217954, -0.0007117611085157328, 0.017628690009600986, -0.0729281083858234, 0.015534189076880709, -0.016398893153474668, 0.04389801401914385, -0.015886121428085515, -0.0004887718385654747, 0.04350479550677641, 0.002088198842677633, 0.028648466127904785, -0.0403452478650482, -0.026325241544903948, -0.019143607736920174, -0.020065194518331657, 0.029406939206013498, -0.05528488769844319, 0.030329163523453263, 0.02481229431576762, -0.04371418209139283, 0.02426866658848317, 0.02039807394896405, 0.018030373666631634, -0.03275778986270153, -0.01553093876937561, -0.0424557899079492, -0.020600943828969886, 0.02841623122694168, 0.028430461630210534, 0.09329358273477556, 0.0017256635673308894, 0.011417923896669194, 0.03286048049594423, 0.019733760833816564, 0.005115354932786326, 0.0015268059095245139, 0.049085226841407294, 0.058275065674785645, 0.021670676575033944, 0.009290098318585781, -0.009728897246884862, -0.007065134664984379, -0.02043263288474092, 0.0700185213023341, 0.03947875599110101, 0.008009399880434795, 0.055266781514151014, 0.009168461281182224, 0.0017677618999300294, -0.005287649182379124, -0.0030378090991815335, 0.03103683754569402, -0.005159326014286588, -0.0065902036265972005, 0.03712861378585721, 0.02613132376640102, -0.05259115250311066, -0.0056828958855907295, -0.0027900745978634203, -0.0022220822241819232, -0.025350409644898375, -0.021815447169482943, 0.03550327811407822, -0.0024678312109125376, -0.024924483760002825, 0.021781982477757054, -0.02973801694315856, -0.013752351075943766, -0.0076854537598554686, -0.03681880771639984, -0.03589204439776908, 0.0010585516409445783, 0.00840784262945558, -0.08402636641160798, 0.016737225291732456, -0.017795249565654703, -0.052740612578901815, -0.053811746029679745, 0.06765916976728854, -0.014079490631870889, 0.009072545151075787, -0.02253194195337572, -0.013944168967239979, 0.01437332335301171, -0.007216878213415799, 0.0008646543238635188, -0.05041576029579501, 0.06969475184966888, -0.0013431314133204187, -0.042799521063519, 0.002808481239870063, -0.05021936765999202, 0.02716968766290912, -0.0015481668251448102, 0.01533423481757028, -0.06072601253104493, 0.018588933841098262, 0.021952207080363228, -0.02171093174679372, -0.04394316386979369, -0.04636451536693348, -0.05803398931487645, 0.05026496782934799, -0.002687812274459688, 0.009765400016845845, 0.029750169807261875, -0.0252729543692774, 0.024124517182822, 0.0037470186555374364, -0.0855823155125592, -0.03596915974718685, 0.007490168606309891, -0.007186342771062757, 0.008445203794189041, 0.07930379321199899, -0.024229628304623854, 0.03333107657742417, 0.03626942171201965, -0.020680971735459366, 0.041701822172982905, 0.01696440424561776, 0.003958914362789216, 0.023411283825373212, 0.030675056359005328, -0.011045864230569663, -0.06762270458547572, 0.02993501706823505, -0.02210799295243436, 0.017756388980225964, 0.0560352608229367, 0.020763605403481683, -0.028210719294663872, -0.020917047379858382, 0.006839017604636636, -0.03375211212934924, 0.016395310569139954, 0.004127255297244804, -0.0463385379286631, -0.020969994218131362, 0.002630550572732159, 0.026729187980745722, -0.004258123065917387, 0.0014228444572740116, 0.04198370514255661, 0.012408725397309417, 0.034086908539952755, 0.026252450305474635, 0.06324317605749297, 0.025758858277202996, 0.010444861247085675, -0.001066360805031575, -0.03660862695508929, -0.0374631831701558, 0.016593777650845964, 0.017538901928272965, 0.0004229874799809748, -0.0129756297049408, 0.030616368708697883, 0.05249241257465889, 0.012344331787684681, 0.022122535987706155, -0.042492566613534025, 0.020626292402831765, 0.018520932675914113, 0.02599014821206337, 0.039239525724734714, -0.054013369661217864, -0.010531655318856345, 0.04164449618117891, 0.012390698072273698, 0.038337775692589116, -0.012214683589736541, 0.05496496949568891, -0.027400854330441957, 0.10015628566328927, -0.01511329331597217, -0.01326265514473395, 0.04338421911043217, -0.0014541225353393375, 0.002969301923075897, -0.026966032086696116, -0.04416161008506396, -0.0027882425832497955, -0.04060158016436306, -0.038360516660103275, -0.048623561482371506, 0.01385639058945707, 0.023791716391607563, -0.035042952986419004, -0.03974507175663447, -0.017269880161187818, 0.028605023989592998, -0.024361787609453626, 0.02118327420627269, -0.041393365501887616, 0.01501808672566414, 0.01655171414107287, -0.03216574897231152, 0.013478440508926243, -0.0402974335719754, -0.028938165756713485, -0.0022461311271102773, 0.03098402126599095, 0.009204925916611945, -0.014132774145570346, 0.009641047583226868, 0.0037102996219085032, 0.045187015245371734, -0.021466029304243343, 0.03629091598781914, -0.04037007995348967, 0.02630068972002036, 0.007709915104736113, 0.018394968208106224, 0.052144811271388015, 0.0008574029327243892, 0.01918764888264248, 0.018203787460614254, -0.05632179546594161, -0.00018593777576297182, -0.03158729626208494, -0.05245389029127688, -0.022500339079148543, 0.0024219774287067546, 0.010501549744664206, -0.017228450802697196, 0.024442940971235047, -0.010582183854567582, 0.050680966435229735, -0.0472391170640831, -0.02380055244490008, -0.013989959549435552, -0.005305700709999072, 0.028845678708233388, 0.04605309575037641, 0.04177317059757487, 0.00886976290821489, 0.031243627229238985, 0.019712750970535067, 0.06505690497814556, -0.006824106761560578, 0.005536754706552405, 0.010211911069640712, 0.013089567263331915, 0.0017610583322624851, 0.054005606275472115, -0.02768405514010655, 0.020376304465766253, 0.06960485840610407, -0.03721858939839902, 0.032018493344214395, -0.05261709644632544, 0.06776499898015363, -0.049525981204812324, 0.04146784694378618, -0.05288595941300466, 0.00266548622136532, 0.05605853772606208, -0.009179389880902517, -0.004198121088030984, 0.009135689832664606, 0.03885323727095904, 0.059391754545477196, -0.0003625440136304772, 0.006803256331490927, 0.004767451166010097, -0.023850071254632295, 0.009725547490181048, 0.0224022617808219, 0.01972406894682954, 0.007921367368592776, 0.00047137452904760964, 0.02226833734651263, 0.00018724877171945125, 0.06517580565218764, 0.01063605246148822, 0.0745751073667017, -0.053869292648454255, 0.007003887056237059, 0.025665773023541685, -0.03869435400162277, -0.007176017639508744, 0.007945416780121745, -0.06053080482044869, -0.014744972780694079, 0.016646864955235777, -0.06344058731272965, 0.06858724481504037, 0.04256708450446997, 0.04268714611637644, 0.03827902721665556, 0.041191641440769866, 0.039573645269911625, 0.03633382301200207, 0.0003766394517877164, -0.025530550321495322, 0.02923890285721633, 0.012347023317954928, -0.044093608526162675, -0.018011320581198254, 0.0022513189078672254, -0.0030204845696608675, 0.02341655873716331, 0.0027505918085683257, -0.027321935263040317, -0.000501970784010276, -0.00017806742754677106, -0.03746169251568095, 0.038388400986998084, 0.005718052006654355, 0.011073068808792386, 0.04410563448347062, -0.0046574814216608865, 0.015674359403776313, 0.01104784356799879, 0.05993759188595399, -0.014744930877363803, 0.01376350722071402, 0.03572282064451309, 0.008779254412660073, -0.03268576647658075, 0.004970474245815747, -0.016066464658841, 0.0006208023690643084, -0.022024535672333548, -0.03697914358716115, 0.010221668543292792, -0.02497625096519947, -0.020861905384386564, 0.005148659273672385, 0.045050233122352956, -0.02417622272454254, 0.020820832432003892, 0.03637898750380775, 0.006562573136838671, -0.0163932967813544, -0.007178670776047737, 0.02015261221753389, 0.0017950649340300086, 0.031026116923085455, -0.01759086868898034, -0.025595781451275283, 0.03817522804567382, -0.002112147125116982, -0.05651316946305708, -0.036575599123547645, 0.04463583117057854, 0.0248115393228615, -0.025048624011437055, -0.027279334489460013, -0.036562048024610536, -0.03130290866105127, -0.015056650190672385, -0.0016393688538951855, 0.054363010196031264, -0.00929746201310205, -0.01524593161137663, 0.06080675063083209, -0.020744039591753652, 0.005899208741933529, 0.025528001978533204, -0.057916975315925594, 0.02641106277150012, -0.03604377922165894, 0.012250205113206444, 0.009928299209913111, 0.004913066499356737, -0.03262867380396605, -0.021589434243820305, -0.01417259288638303, -0.019135697728212657, -0.05165121204260358, -0.08337338490536396, -0.014767697539757187, -0.04100163327699655, -0.002851158158039386, 0.018922558781055473, 0.10839775592029938, 0.004160639945101454, 0.011878910054102276, -0.003646318994462567, 0.010722425291487665, 0.001167123370420643, 0.04712942620403465, 0.0404593918612136, 0.0163842779285022, 0.05018796530899244, 0.01772126476435421, -0.06651392503474839, 0.0628180398762673, 0.053468093657555796, 0.005998329219366668, 0.035533451402980545, -0.020083862543616087, -0.010509671392208875, -0.014958354837725715, -0.06051233170764324, 0.09065779813041598, 0.010689956278195866, 0.0016135327834780665, -0.005429051000611621, 0.01538259621133793, -0.0015386483252059575, 0.008786469947630495, 0.06869917919314943, -0.015383126070342831, -0.007731482494523022, -0.027063305868062545, -0.04785135250685925, -0.041215153974000454, -0.04966505766469876, 0.018039929160023865, 0.0369269086034058, -0.002388612995377176, 0.012281396164136073, 0.023331529865730657, -0.11469678864162994, 0.01924867890074813, -0.02008919409395083, 0.01637346980053507, 0.01830050509961667, 0.01853866795644264, 0.0049959073964135695, 0.03009628613453847, 0.017336287016862652, -0.023800322100489286, -0.0576885596822325, 0.020457517056611113, 0.003061675828534065, 0.027336826084969814, 0.0025168088383118984, -0.020371851639325856, 0.014448946801106742, -0.033772126433957425, -0.022418066124611984, -0.01832915150277681, 0.004026440368662286, 0.03375609171432216, -0.03115264283692697, 0.0177695630082554, 0.006262575589226443, 0.04039196379916408, 0.009233148536663856, -0.04031948988037164, 0.025954433371656226, -0.00866639280286472, 0.047751343917679115, -0.01466945454009921, -0.019107564977168617, 0.016963521747445004, 0.00798826339698559, -0.011942135529303562, 0.008925874540781555, -0.004417229243507315, 0.014540059323402446, -0.05262232972185011, -0.005758445890360345, 0.025022086925323175, -0.0088246004890455, 0.0013021820207053077, -0.05450140177306701, -0.00354518727425241, 0.003918840110059738, -0.012525827156963291, 0.019889649567411856, 0.005324125461427035, -0.013781634889515896, 0.020068008044571303, -0.03726099607035369, -0.0037410293285305065, -0.02544982175520219, 0.005108144484653938, -0.062112963233675086, -0.05059086986600131, 0.00987961418227097, -0.016185688504441346, -0.015174897808725808, -0.02088885160166976, 0.01927010177251419, 0.03162119149464245, 0.010549017471333634, -0.01417119133662854, -0.012151296027075926, -0.0005469990843245198, 0.010030862534548865, 0.00896730142841334, -0.021909221470815737, 0.02809550853296408, -8.306524023809517e-05, -0.0012091052352073194, -0.0444450163843177, -0.05453754979707345, -0.01396925846954064, 0.0002773326816046058, -0.05610502253120422, -0.028635943021830985, 0.010661834066600226, -0.029037620839389655, 0.015139170719009491, 0.020463718107632624, -0.03106101718516608, -0.01040879568588795, -0.012873555237942038, 0.0014055119433325958, -0.0045527274824216785, -0.022860969530141516, 0.002557105439993138, 0.003035510149338197, -0.017100826914623134, 0.005358947256140371, -0.036688935258127615, 0.02612888817547883, 0.03081797637125553, 0.020681861390870034, -0.01826099220852966, -0.01701548456380598, -0.028778953184336566, 0.010674484239163765, -0.022379960811327235, -0.0022520726705501344, 0.00921664688797936, 0.021966089212811773, -0.0047284781186080855, -0.009616910679466378, -0.007615549399986542, -0.014047909623827976, 0.007451523080037449, 0.0673346345294699, -0.01420608998559701, -0.015550991520781189, -0.014961904123833431, -0.0005994323709865315, 0.01878486223357407, 0.012254508185530257, -0.054613282170860936, -0.028281566794656453, 0.01753982090334112, 0.0004272477634314466, -0.01851548659773727, -0.022577952652179167, 0.042951380188779045, 0.03554185523750142, -0.033794383403844136, -0.03850664577890465, 0.014135597229028796, -0.03792152271610988, 0.025513111273484226, -0.020871279469287372, 0.001769972231329449, -0.01512370977573126, -0.011843668006869649, -0.007520168739482192, 0.05081296479927913, 0.04231105664296747, -0.05816133738826793, -0.004749846573762342, -0.003161656491905871, 0.004405377934719539, 0.04977669490252759, 0.02202325726057793, 0.028469307252778762, -0.017930223382979196, -0.008636834774127765, -0.0522799836087357, -0.03299430780226192, -0.031841068814027734, -0.005762578972660625, -0.00041879564895088307, -0.037155770965868785, -0.0005414922405594326, -0.03480819481726788, -0.03104906273114779, 0.003817002082406703, -0.015785992350571827, 0.05385493220247522, -0.01540570424995071, 0.02863404732803712, 0.008425817746456348, -0.01735657976968747, -0.007334340161946817, -0.0015524118941669422, 0.004122949983302951, -0.047244935066274654, 0.021196527095712212, -0.008900075047781328, -0.04574030624224767, -0.005357595645667853, 0.0013265105021830921, -0.0006437604748843151, -0.010853721968342559, -0.014242577048827657, -0.03377980818618994, -0.03582916907117142, 0.032533423338327615, -0.018310079234247423, -0.02017964877194674, 0.027333929904271765, 0.08121200796776311, -0.025536463047281518, -0.030039842573846648, 0.009741776583344998, 0.0073789637378839186], [0.0008061803326397336, 0.03368893042906682, 0.03144395163767602, 0.042609031946589174, 0.037509946516911245, 0.06696443761020585, 0.029238240402168474, 0.016034588576769777, -0.006304769207010571, -0.0017730331485263581, 0.027634940002136316, -0.04037108484303681, 0.00802073586112471, 0.02696961651895689, -0.009880372234719189, -0.007874417603524945, -0.062326567477609195, 0.03691675453161641, -0.014390921948308577, 0.05334335920844405, 0.029668248368733167, -0.006749080009662629, 0.05006212249292442, 0.019392614763768367, -0.04031657165083216, -0.018792276116038127, 0.04971576953957292, -0.0206319973228801, -0.004403378361001062, -0.04287764419545627, -0.0653501205444166, 0.05817111752071228, -0.005202707923822555, 0.06323051200774693, -0.021079247469249652, 0.024525515192294123, -0.0021001668882423084, 0.03295122225017152, -0.042102919354183095, -0.0009227219603966941, -0.0362632049071422, -0.00339486002087892, 0.032509527093606036, -0.017905647542333026, 0.07389713256203854, -0.04615480748504136, -0.03695576144660844, 0.03292393532467507, -0.0017881691919908113, -0.030713546989910943, -0.036924240177451925, -0.00874806654939446, -0.04903303379532313, 0.006548844747258021, 0.019152616568209956, -0.01107273231986307, 0.06097165555678093, -0.05758243479822744, 0.027492901718970574, 0.011626141651148895, -0.05068143521847249, -0.00560806263577767, 0.009623904611933623, 0.03761976907803831, 0.017054375021152143, 0.00019811835124720347, -0.01689653319487896, 0.06192588126857526, -0.005204005039379188, -0.01460983121935413, -0.023636315887199888, -0.0933378820455942, 0.03852687964899336, 0.005264528306587949, 0.0017296400404098388, 0.006097786404450871, 0.04997531338097292, 0.010094110605581708, 0.04926141257409599, -0.021084819686162458, 0.026756100854401842, -0.04109361696600307, 0.03666427235827209, -0.009501895630286484, 0.013167667567927754, 0.004881798368439289, 0.022244096602708263, -0.02484073315493445, -0.009485907466153503, 0.021613802860622845, 0.029288916630979822, -0.0048837580986363475, -0.031538657091396166, -0.0036569313282508523, 0.03473006894189807, 0.036488558667524464, -0.004978319755382758, 0.05921099765441641, 0.0059452082545494115, -0.009382215308063536, -0.04379433556991019, 0.006882998990701191, 0.02880668194509809, 0.017421996159136498, -0.013051294864077159, -0.011464034099096604, -0.007046660189744192, 0.017201040810081985, 0.06132907540196288, -0.04800841593495927, 0.011071483090810248, 0.01799580093223345, -0.000681994165685762, 0.02440569304970274, 0.0020478413514306787, -0.002044499461807554, -0.0064382301875132155, 0.019291673977772643, -0.005863674076224676, 0.06883215002048394, -0.042861184881599956, -0.09219024757142302, 0.029842606122716418, -0.036576194361024626, -0.030219256079510207, -0.02618527872489306, 0.08329149579495, 0.06385445338791645, -0.010337593029704986, -5.163200431347603e-05, 0.021216041080845523, -0.0019090027136704586, -0.027444165170136856, 0.03713467737020694, -0.037603781624133456, 0.024769563827877435, 0.013093697395830734, 0.00871330520890204, 0.011570397430947767, -0.034656371706784546, 0.014622241969952755, 0.04184513343803564, -0.050178779501062365, -0.015201364796290697, -0.007400097819600447, -0.05499165791653846, 0.02647468786398554, 0.020628678278590145, -0.036581722483133086, 0.03395693244983607, -0.028542179003687256, 0.025332915430586505, -0.007645411221179592, -0.04157608891590476, 0.03598539906940333, 0.028930190676736973, -0.007368401912727064, 0.04026806173108013, -0.008065033813915235, -0.022403188884776902, 0.0372431405972216, 0.007834087126379109, 0.013764307191638108, 0.02952390433377197, 0.0022290558463524054, -0.006992535461629826, 0.0179978496914082, 0.024214924441350705, 0.0036440508923308735, -0.032732509251153286, 0.0020838584149707393, -0.043239904179216, -0.003985134030929585, -0.0088768890623465, 0.004753274971394814, -0.04673277345793616, 0.03468060720725573, -0.013602735791517103, 0.0031655108779341298, 0.055242945179653155, 0.00040297920874899154, -0.010021133042771486, 0.006742925934400627, -0.029871011708563176, -0.028351549235141285, 0.00624635606202998, 0.05336196213368213, -0.01248239061385966, 0.0576868055048393, -0.004757086606831782, -0.04834421263999078, -0.003451019018446607, 0.006258210124865372, 0.003793438827345983, 0.0139046384463683, 0.023010378457920394, -0.004905449849666871, -0.010804044678387024, 0.04383905613594763, 0.030298866486007915, 0.06187004581161721, 0.0022137786187429766, -0.0067788525218131016, 0.025024690539887175, 0.01767881620041627, -0.01623166044089664, -0.011528246350153948, -0.0541922713227693, 0.024450967664704108, -0.0049302910140126745, -0.024184308806321258, -0.010057920264444804, -0.030106660834626684, 0.028181360851795373, 0.020525580823499746, 0.009050056402082023, -0.005628445896131611, -0.009100497027372551, 0.05766161365052026, 0.010741179225142493, 0.0430409243336327, -0.04419771600171032, 0.014889126470653564, -0.00955446605251382, 0.004396857140004434, -0.0028689312663475568, -0.04646734565879336, 0.07284711360909046, 0.0293855029077692, 0.02301267449718264, 0.04346282271875344, 0.03488007671971673, 0.0023955211532144492, -0.05439784175089145, 0.05730829634860319, 0.02172363753897287, 0.011327056357710238, -0.005385739752538598, 0.0024549137082356502, 0.04259117359569025, -0.029223811855393772, 0.00757796050274771, -0.04388676169461083, 0.0490444815464166, 0.020332013128705004, 0.018852532422245, -0.038928156411799086, 0.010934197424811987, -0.005332069620341771, 0.01573642706338989, -0.03883553618362448, -0.008436220624947994, 0.0007095453887960117, 0.002253576487781978, -0.024342441963048942, 0.0026988026815864905, -0.030870446927778558, 0.040254978139998635, -0.01999185885275797, -0.04210871414274424, -0.010540097705808963, 0.017799133632633697, 0.007088467211846089, 0.06331325682873593, -0.02993899289796756, 0.04991378241066906, -0.014096053216545279, -0.02793248323080406, -0.005734568893785541, -0.04411980442509814, -0.019685947087129847, -0.03584978228505609, -0.026191200058144855, 0.0455214340286847, 0.019779298201290124, -0.02811052482969426, 0.03494465997556524, -0.005137840153836969, 0.013852394716497194, 0.03652840360183261, 0.0011797474183047364, 0.019399430070685945, -0.05245151489903019, 0.061843906526768314, -0.012423832035865017, 0.01634555063819021, -0.02634459513046193, 0.02922055022205893, -0.04880699625981898, -0.011675618720714105, -0.01836769968723189, -0.0612455237935144, 0.008620635373667904, 0.01660432486822779, 0.03267657624126177, 0.030731657629082997, 0.0567736281914762, -0.013983662062877465, 0.027877259153433077, -0.025269988671324992, -0.007133886134097829, 0.004424622520385221, 0.032088567047091746, -0.009798853494717786, -0.040893221091909335, 0.019130952566665797, -0.03013631460343858, 0.02127625527235876, -0.013458335697556065, -0.01098537184848967, -0.04942154185695195, -0.023587827489208735, 0.013116882412882568, 0.017204808629664264, 0.007919767524558636, 0.007985174053546089, 0.01097984924114652, -0.019901164576731446, -0.028351806235432188, 0.02440223380896711, -0.036579225868440715, -0.0193606866965369, -0.007855781924921864, -0.02362772677103098, 0.014870610015860623, 0.020384709546084283, -0.0001675224025293829, 0.024261246075738572, 0.04269421030986308, -0.0017830880084522861, 0.03015774851406247, -0.007214430592746748, 0.03958747891859166, 0.03174511197204021, 0.004090640522965934, -0.011739326936732275, 0.08340284834533963, 0.0052891881266376225, -0.010954581768877586, 0.005852621296008302, -0.01658717592403661, -0.06885896391306227, 0.017870708826724098, 0.02820638438735018, -0.010622216083516154, -0.03570407526843364, -0.0499964656802078, 0.06910437139642424, 0.011928310018703652, -0.021235813193022375, -0.010244131042474423, -0.04777102651041598, -0.021639385673213354, -0.03143886181064253, -0.015763299042919215, -0.03422371640485643, -0.03416113812733597, -0.04116530975743989, 0.025807003144682372, 0.049637875137877915, 0.05524906463021666, 0.04094569883306731, 0.06403242303200693, 0.005018568141920254, 0.0769740669211952, -0.006889921831696042, -0.01410786998284963, -0.018416546665787504, -0.01517619046856766, 0.04693252972537107, -0.010727048376657779, 0.08442922083398535, 0.062112902850625144, 0.018451166532083417, -0.012915986702221032, -0.06221709039606051, 0.014631249837568756, 0.00678035842456646, 0.008677709300344522, -0.009738145634239345, 0.00033323072147556365, -0.012896341275116257, -0.017018306327412726, -0.029343934387139878, -0.02526588890703315, 0.01060001706243348, -0.0007644274880697116, -0.023684216017554386, 0.008118369086233163, -0.05452888797781802, 0.009163515533223079, 0.04625729296113877, -0.04294209326438773, 0.07072254076190762, 0.000937480869952427, -0.0004045281441374688, -0.02842585185382883, 0.03169034578799068, 0.024990773434298487, 0.01517774733982203, -0.03802650342389969, -0.01307264497671036, -0.0062091098693206215, 0.03906291476754867, -0.018744709800276652, 0.0002486019721847036, 0.06891433564100326, 0.030866050612081686, -0.005421807461275888, 0.059716075055711124, -0.06302101720008713, -0.016030798277143196, 0.016441212151031864, -0.021493081137448425, -0.00601361979615698, 0.015859516862086073, -0.033213096508195504, 0.014622728240686873, 0.02857279243164525, 0.009901510061738693, 0.002396380672884467, -0.006968464488298354, 0.039233186562602086, 0.016615198207881456, 0.039348993914879074, 0.01486947827838154, -0.03996526709243051, -0.03501845081240181, -0.019926443901962795, 0.013336129529761646, 0.007430393655340416, 0.05586165268170602, 0.028051481575270786, -0.03662693591290608, -0.03300907842139468, 0.048741301186328016, 0.01807919418233161, -0.0011575223272295952, -0.058562241158029354, -0.021076132496507773, 0.0156816566070968, -0.02683462820189762, -0.010163095118314255, -0.023694594643910428, 0.006661278187541271, -0.012871243465831775, 0.026473548783776384, -0.0058478152040326034, 0.020742671356139965, -0.04398184349901983, 0.06406266510877534, -0.052699776422536676, -0.028372101696891923, 0.03597914860929579, 0.03148889121865643, -0.06672618798327962, -0.06915351059840438, -0.01946226350761449, 0.019934409003821957, 0.047746546554579605, -0.016765792496055587, 0.05615754987696718, -0.01999841125761987, 0.012724572210456088, 0.014730017175171514, -0.02285419169970852, 0.019613059868680168, 0.011368564044131476, 0.017617594787091524, 0.014420100423924873, -0.017239575143701863, -0.05220171722618091, 0.03669263403315713, -0.011754774811686592, -0.005794827090147857, 0.041161264172277466, -0.014437175303900762, 0.01910949150427941, 0.05688410380853065, -0.0022686422626577198, 0.008863038104754254, 0.004614961048337363, -0.0005303435283878638, 0.023961456463258694, -0.04958488512970541, -0.04858657336051309, -0.041266589711218465, 0.013658166959436351, -0.011744542490451928, -0.05983236319114707, 0.006608334204254759, 0.006156440486893331, 0.020328116505070125, -0.0739672868984494, 0.05376833977410342, -0.03464830343538412, 0.005496057624819183, -0.0021901848905186046, -0.014961144338196484, 0.018071949704563658, -0.06393842873010482, 0.010967295663140047, -0.0018137763809576365, -0.015563925121151997, -0.0005700100492111518, 0.029127226424351924, 0.015861054779522644, -0.0292921518086303, -0.06009086638761335, -0.022062854874930076, -0.022083094946809934, 0.03528089659868153, 0.0212455501352831, 0.058096832120707015, 0.04577359398532814, -0.020265692244300814, 0.035133502260198436, -0.04087039789860145, 0.06201848093375825, 0.006179909013338272, -0.03368574312140691, -0.020408085069426454, -0.019776982934099983, 0.0031463149262728456, 0.006060916707220379, -0.037789761072358645, 0.029396749350854665, -0.003695907513169225, 0.034899907644961696, -0.020643361103041408, -0.012562285265997218, -0.007475534146543615, 0.035708912651482995, -0.035317428948256055, 0.007094261203813931, 0.048772575662319156, -0.027509331747750325, 0.0036893226828428887, 0.014721598614583982, -0.06001831435284283, 0.015081073632437146, 0.0550872387849865, 0.037598392571103166, -0.024184292499053205, 0.02084015046077603, -0.010265574579954119, 0.0023259348398000264, 0.006396160799651104, -0.029358367934705074, -0.00356015719098804, -0.02868990119425304, 0.023500355633258932, -0.0017599114784240342, -0.012213881472141808, -0.008508854692710696, 0.03216534000091617, -0.029892812999193944, 0.025209906658893586, 0.04591892379003326, -0.001958781812918064, -0.027182781227588774, 0.031041583880031025, 0.024449165972358414, -0.051819658286311646, 0.016125296682808647, 0.00263924597221994, -0.012967864440876114, 0.03534977219608054, -0.04336533242979807, 0.0017721339984314785, -0.0084058499823166, 0.01796174384105251, -0.0123238148104574, -0.005033256759488562, -0.03596960640813341, 0.009433825811501158, 0.02160132837133233, 0.03794694665043989, -0.008483507499160214, -0.03644404798761198, 0.022152048718646823, -0.00929645380747648, 0.034551280538862146, 0.011106599073380831, 0.025822376482570734, -0.0271316258852647, 0.04550044654525831, -0.07951223770582515, -0.009723583051464823, -0.006017384850979322, 0.01203134388854832, 0.07679005706788203, 0.08872468106018964, -0.045731842178484555, 0.024097554023554908, 0.0051049523669478205, -0.048264463285461155, 0.046008363024496984, -0.03318454713253173, -0.013045570179972977, 0.03252770117442812, 0.008506673518283476, -0.03836593179500658, 0.014046401837606602, 0.02909552827202313, 0.01591641371771013, -0.07373609960307906, -0.023791675646977914, 0.02865026817689601, -0.004263625769000556, -0.0031292979010331384, -0.011049213962980713, 0.0035588254876105525, 0.0427696539443395, -0.027226308566212193, 0.008760358354132636, 0.008528813587412101, 0.04655331407719315, 0.020214494277642846, 0.037511678113394537, -0.004510871159766099, 0.033064882496138995, 0.042471736687084906, -0.021573554890802354, -0.06667427439218743, -0.0049917138276098765, 0.021244471930740607, 0.02823502889005211, -0.018136900323984476, 0.004797880184113961, 0.043853879481639366, -0.015480401907983419, 0.006691992013057455, -0.0614128612178576, 0.037197501808447615, 0.05214102088624345, 0.03454776966925125, -0.038283954317992856, 0.018853843225446282, 0.07119288175575433, 0.0371542680165463, 0.06979105190838987, 0.00255848877275827, -0.009965561437690318, -0.026517461565047324, -0.07335598276029956, 0.0441943274001797, -0.0020897238154267512, -0.002797490034322697, -0.003906794496005079, 0.012638848202309622, -0.008600663456340175, 0.032145761426459155, -0.02422174465711871, 0.042350177223286566, 0.01717987027015588, 0.04446320132505356, -0.007506991720839648, 0.024452435813324618, -0.004450632662468097, 0.06410215732038874, -0.02567262182261784, -0.011157219538025091, 0.006266586065165034, -0.01546300857689423, -0.03976774580620598, -0.0628893731989039, -0.037317985121660596, 0.010027709049933754, -0.017486977874811516, 0.025119302177690348, -0.005416756490620263, -0.03859828878324348, -0.05899653938634266, 0.06693543401230945, 0.009479492382484286, 0.04098727557991081, -0.038685627070320285, 0.007799288210200059, 0.045306114048927114, 0.04929779568940164, 0.0033870328627491446, 0.01036078411468602, 0.01134800064811772, -0.004148480590659808, 0.003399667894269579, 0.014666444539568838, -0.037844257850859146, 0.007755974020236719, -0.024159406878389446, -0.004406834467638411, -0.018267399133914314, -0.03794938781321395, -0.0010367842482668296, -0.004124557243127117, -0.037261453391359764, 0.0017278269147197748, 0.013485441036331758, -0.0356780897923719, 0.019511047726220827, 0.0027506475321865936, -0.007134476454540074, -0.04889988899598907, -0.04319677555640734, 0.017084694548574614, -0.007304728275055985, -0.0576731657537455, -0.017945240073469877, -0.008120631901873857, -0.009802913554059677, -0.0006692656630651328, -0.0028716472730558416, 0.03255528371269187, -0.0567186296194245, -0.011497525842484957, 0.008509631897147998, 0.005285236374557814, -0.011404524779761102, 0.024211047771929617, 0.04169096584304746, -0.007757987091402028, -0.0444655388268826, -0.016169698320512266, 0.01655253434815356, 0.03255915447606781, -0.0073131945115094965, 0.011307647898088025, -0.048472900775265844, 0.027699493525264445, 0.0244856277157574, -0.046583200478696685, -0.022775348740844713, 0.016109531513712607, -0.028639831163250567, -0.0016006554229182788, 0.047383086453472695, 0.0033289775222431605, -0.062139048984499574, 0.016303682944174544, 0.03140569549392105, -0.025877600650436858, 0.028461825068231278, -0.0022264165778686173, 0.021962988556885332, -0.009787338415780474, 0.041049547057233106, -0.014115201909780665, 0.0020649512187065526, -0.00924479918494631, -0.020974120090590423, -0.015537670341998995, -0.007303271750573541, -0.007142778729644329, 0.008065825861234648, 0.05105851254824938, -0.0438266955978521, -0.04274939655435858, -0.05085965603153736, 0.0022312825840828335, 0.07705389193878957, 0.028804224339424, -0.003895197057743312, 0.0050004494387678785, 0.03689393185948743, 0.0001605923031295282, -0.019238861808459365, -0.04563201216854111, -0.009871665344647389, -0.04251262457540526, -0.025347485850635645, 0.004918459722949839, -0.022036312602444923, -0.002181793659696218, -0.011654875789191787, -0.029809794575253235, -0.004145217642421838, 0.028258888435619155, -0.021070511305553957, 0.020629303149070955, -0.014475017817694957, -0.0028137467019246102, 0.023131091767325484, -0.04937954207926386, -0.00985061820277223, -0.021867870225957355, -0.008044764297973782, 0.04840091949464256, 0.022215005739424417, -0.04375679095717922, -0.012187844269360348, 0.008865627723393141, 0.019139532124859727, 0.003259817901812018, -0.01397345615768775, -0.037081716540184544, 0.016105489896183395, 0.006257253442356252, -0.00911394747582055, 0.023621710909294446, -0.029404037353614812, 0.014292753689859666, 0.045240458110601185, 0.006271892097252821, 0.029633569998042956, 0.01618750946917451, 0.012084963678761374, 0.02451643879836517, -0.01232383636098913, -0.02838142118960854, -0.06014806213291811, -0.006879772326267718, -0.05721024582861812, -0.017990004417427194, 0.017315949594886977, 0.0033869947804564815, 0.017454588121854058, -0.02143587474966386, -0.0038148599035567965, -0.02379439881089452, 0.027370650310643833, -0.03291514044144255, -0.012529199532756746, 0.005082206607425862, 0.04571352867062613, -0.03173205598192889, -0.005914150168883038, -0.058433912121838585, 0.021177532067206524, -0.038032086073658294, -0.03149104094349897, 0.008218043531321682, 0.040152050398768256, -0.018405234095923453, -0.02331270854260809, -0.08014461171667954, 0.04828435663317823, 0.04553692940374903, 0.02197931886996738, -0.00027422683592119503, -0.03712673651182566, -0.011907163956753761, 0.04135090117916689, -0.03105652509307669, -0.02979884590335608, 0.029419876108549795, 0.03415111023640047, -0.02423409041891717, -0.0008802497503564645, 0.002626414461351476, -0.04584024208849956, 0.0331159176680011, -0.029717933523118616, 0.03237244387250651, 0.012825983218363193, -0.025652199511605935, 0.01998199280326697, 0.025324956467355435, -0.01873652396456426, -0.07854544020604481, 0.02261824608418532, -0.016939849567509806, -0.03436667031360999, -0.02777396677371066, -1.6291878195517056e-05, 0.09321870870712343, -0.011038404415254948, -0.001757667221330913, 0.028850353258124288, -0.021857177043484107, -0.012246069125483861, 0.002518772743601274, 0.018990744489162794, 0.053631340805044905, -0.006536987738318133, -0.0062236277136904754, 0.03013412516483607, -0.0008007312576133247, 0.030110395625143838, -0.005036667804136405, -0.016718787685965457, -0.04011173158465617, 0.0638290704505462, -0.016290091520590244, 0.016294103841038396, -0.026620046381419522, 0.014431633592455041, -0.03430597741425483, 0.02493867361468891, -0.008106177989558889, 0.053896161923800645, -0.016215471794179027, 0.007682163264155905, 0.03868663949824896, -0.05023406446102921, -0.010907058925538123, -0.023983687507123697, -6.304629611038761e-05, 0.02062160957267279, -0.04622108669710117, -0.015382199967336944, 0.0032703495803395656, 0.017631076373073748, 0.006950148337591659, 0.019406818312962343, -0.032580478513951724, -0.01260420374647255, 0.02858352468171502, -0.022533246597418323, -0.03751103588299303, -0.050158709546023306, 0.01989233102113289, -0.05909648945691884, -0.0391440047362617, 0.0383187337737889, -0.08983790296578723, 0.05458067490692015, -0.010291845420905714, -0.012850324682761347, -0.008389714909270625, -0.026887229101251475, -0.019257677632641714, -0.01199223293272209, 0.05108609214261842, -0.03866788322022069, -0.01853187731941721, 0.047606542827077665, 0.01985790346983669, 0.0012658151011049137, -0.016084618819035012, -0.09156911149690194, -0.071957107727413, -0.05944888800305774, -0.0038639796482589452, -0.008332206847234744, -0.027163936332850587, 0.011727525232955864, 0.062305435494221156, 0.020395934349746837, 0.036518268558839856, -0.006978676005509901, -0.0133511413273834, -0.0601073041960399, -0.02477883607717524, -0.021387827006178455, 0.004629901220662155, 0.007897220006326894, 0.01368106822155331, 0.0335223461704032, 0.005437737705509218, 0.046575755711368474, 0.07741454628743226, -0.02093349275039195, 0.03229164314606853, 0.03853956690494557, -0.04157614414060258, 0.01172427935814777, 0.007664173144045712, -0.01494579065846186, -0.008675959861764546, 0.011492732246895539, 0.01834637003509682, -0.0027797137939854374, 0.014023189280569703, 0.014612843820832052, 0.02735620509896124, -0.008832747250959696, 0.012635651967317914, 0.03741954017595958, 0.034066692069348314, 0.010662181413421956, -0.030801612891100907, 0.027004726349252144, 0.007714647841730436, -0.006390996504618239, -0.036208361894789105, 0.011044465373388474, -0.007346082997836091, 0.03672613672274027, 0.025450021527291963, 0.0005059003658910815, -0.038866675112587075, -0.008078282639481417, -0.009297336898531551, 0.025889576843261142, 0.009137341089649415, 0.0034705036429404086, -0.007199004928143189, -0.03687130824012799, -0.045068799237560214, -0.021022525430036035, 0.05633834464052225, -0.014081828092830479, 0.03373643250707098, -0.008680754260742706, 0.0018578398375719662, 0.0052407469777503105, -0.025872594521325974, -0.01146526080238372, 0.04817154532339191, 0.05216566698787369], [-0.020383927903994768, -0.03914049799327216, -0.013303130029856253, 0.03240347402385443, -0.07931592848120189, 0.02571702656777039, 0.0643416981551486, -0.006548705854986522, 0.012017130090487667, -0.005778872419741559, 0.037501843941435124, -0.007140909010821223, -0.026854840892549917, 0.004185920531168331, 0.035233053121337614, -0.02564305592764872, 0.02182288696181532, -0.01864460207184237, -0.009737470963951715, 0.028619824389206204, -0.017899974038513643, -0.025500917180436095, -0.008933963220031477, -0.02203067909244308, -0.010660471756294719, -0.017042525559594072, -0.004526758742851919, -0.008642692933350519, 0.04154407090539697, -0.03611499392571479, -0.024382446372725936, 0.009345661419501994, -0.04953042854864899, 0.010008660374918325, 0.05630168314827396, -0.057657207748953915, -0.003359326110058321, 0.0007347218389702123, -0.02253285992845542, 0.05561581519813078, -0.05292577872518468, 0.015744095814342815, 0.03072212734296715, -0.025159225530925297, -0.015829437674758008, -0.027896311749981098, 0.010132557935711602, -0.015982553579191237, 0.03683760303134778, -0.030714284943941607, -0.0026681492130579943, -0.067197408424196, -0.036851105473729245, -0.025333917526987534, -0.010064828418289064, -5.494408543779174e-05, 0.05012818280487011, -0.002484449786762875, -0.04673907089055903, -0.04882944995481066, -0.007891495306852396, -0.021680323823137723, -0.022383695831977725, 0.056553782862118124, 0.01853942493956038, -0.0182452571612903, 0.023589442518471906, 0.00045776104747902776, 0.03778419776920881, 0.007691635375169462, -0.013197028498778502, 0.017011157386055808, -0.007914660500909595, -0.024811812166667398, -0.019282718508707948, -0.01896693277369537, -0.04259957063585087, -0.08266892177090965, 0.02936780114280875, -0.008470939781518687, 0.012206060966956457, 0.046178540804887175, -0.025648023415437236, 0.00116748700482511, 0.06155981335769697, 0.06359576739155774, 0.0008933475248086506, 0.009702432643170415, 0.08824501220348385, -0.0057863681878242736, -0.022529470356801286, 0.024260812629041874, 0.02569027098470111, 0.06197468888598211, 0.0037193793970223717, 0.028271459063924326, 0.01652003542133973, 0.007400736250126817, -0.012828622395371247, -0.03455710594582853, -0.04114417188687289, 0.0030127701370539738, -0.03267840952617286, 0.0164459654596609, -0.016769958437696457, -0.002345755578308596, 0.0004336156157556028, 0.024585235287930003, 0.019455891553624673, -0.0055623492275337005, -0.007721154557692022, -0.0056153816153458325, 0.01074374616292391, 0.027778953894911185, -0.02100617039542154, -0.026125460347442425, 0.0026288896851459093, 0.024508440295408055, 0.010321642672801463, 0.010399505013161966, 0.012482842245635537, 0.04711202123988664, -0.017483675493826657, -0.05559581889141205, -0.025696072747430285, -0.033653825833109996, -0.01194762392750671, -0.005705562732865309, -0.046343642289605524, -0.04237678001258772, -0.008596472433471938, 0.030382342988947257, 0.002436621349838573, -0.02081377182440089, 0.033909097320585856, 0.010359134249108967, -0.04200496041862781, 0.0046485469160677416, 0.013140663342481852, 0.016506180152021776, 0.025276029628242266, 0.019211397370047647, 0.01773443715840681, -0.0085520459411775, 0.039112421873333546, 0.00115755917860622, -0.08783116177606601, 0.04904435974246961, -0.03136375379655827, 0.01227644975477302, 0.05162797203122105, -0.010387015461516044, 0.04118631279242062, -0.028379095079822506, -0.020026862960327418, 0.028937057655822187, -0.021095258844336065, 0.02008273740185102, 0.02590640863588237, 0.02215778146079922, -0.02325522786776671, 0.007696734975007988, -0.011445125744149472, 0.0644849192844939, 0.006252165942680908, -0.014078716115210055, 0.012563539806949837, -0.04529562186113342, -0.002562738869620322, 0.024982194163909656, -0.012096075585976059, 0.0027545192251701156, 0.04302953441416275, 0.01280639225269576, 0.038898884308170546, 0.04585284591506293, -0.03187165288886183, -0.06562075920899933, -0.015224915621653507, 0.020669103170586324, -0.01139160221548719, -0.03575099255258808, -0.05721623816493934, -0.0464880018870039, -0.035553081570228864, -0.01366059233429537, -0.03336872636406655, 0.015799040889108704, -0.04332721421093192, 0.03245121675955372, -0.02956698980498422, 0.005151903385991835, -0.026323635850459555, 0.04729383260739232, 0.05319203797302895, -0.022500063723659664, 0.05862115220694229, 0.017186003473764765, 0.020840447298763047, -0.006590755728514263, 0.003696873102996618, 0.02819903325249033, -0.006616404821742728, -0.01757807850212672, -0.032759262638336076, 0.007274016770144631, 0.0842988790615144, -0.061654152070471556, -0.03564595436657792, -0.02195399273079173, -0.0135229026670551, 0.006902934555169115, 0.06179600564186666, 0.012902268430201244, -0.03094538721259594, -0.07308904585903553, 0.019882312588394777, -0.048295511955170796, -0.01968123059275747, -0.024261256179497044, -0.009399125827896721, 0.02093635651863246, -0.06121666615059948, 0.02544591685700288, -0.023565954791035626, 0.006577879866869685, -0.010098320692543487, 0.011354071809009051, 0.0631477760917091, -0.012736340040056443, 0.04324916311792595, -0.018523181790668655, -0.05166475534614414, 0.01898041521244732, 0.03591526122309054, -0.030718310694076295, -0.04348434031182097, 0.019041086187768036, 0.017572193333732763, 0.01657393982384416, 0.0514670341196543, 0.017796315483232548, -0.008580015115779882, -0.015296358587495, 0.014623276631035914, 0.04127666564239568, -0.04242426994152844, -0.01143439309628577, -0.0010821750086099772, -0.008701981244769716, 0.020792588172837953, 0.05469863510125442, -0.012472583289013555, -0.06488998492208273, 0.01719034935958531, 0.03246584789655385, 0.035552960952272034, 0.00907887116381961, -0.005243834988241083, -0.01233645502107288, 0.0013183621456291927, -0.022658722852188673, -0.021133231664209134, -0.060204258804911266, 0.010137084935360776, 0.019892236290390556, -0.020946446161251162, 0.03480885627571291, 0.009236424901949127, 0.0033727864005723496, -0.0362509769334298, -0.021022061859959876, 0.032988020117747714, 0.04073940144786776, -0.006735928528663622, -0.010485321575737248, 0.0459235293806628, 0.0019484114037910255, -0.02864010151117101, 0.06120960238282802, -0.006135766554555536, 0.016846254936039105, -0.02921740623737107, -0.004398903679715669, 0.025666351342930167, -0.04997347682359245, 0.04081193051377301, -0.0028034030270211382, -0.034121277727554425, -0.028079022254652343, 0.007081956790452034, 0.0337895879748621, 0.05575109271881148, 0.0016563366066729285, -0.03110274675743512, 0.01011399546368116, 0.01111332984392769, -0.007762845902125209, -0.03073506072751446, 0.009176786474923735, 0.0036019634206247327, -0.04523513937872489, -0.03913227925646766, -0.024012962230806633, 0.03790920107322763, -0.01952192394471933, -0.03576035308041978, 0.05519482081955862, 0.006118916693830825, -0.05741330746944833, 0.05794921681403581, 0.02270008982062903, 0.02805025198552973, 0.02006495405422955, -0.04047263432584372, -0.03674196729649809, -0.019234967543348955, 0.0039757923584057275, 0.026674152575373523, 0.006331533452550251, -0.014469347085025265, -0.02110366646119535, -0.025040932513478234, -0.01871886873849417, 0.041236476525100804, -0.0200965968579984, 0.008352215512452906, -0.0745659370215269, -0.009318303079281482, 0.00969071033658819, 0.02953374883028487, -0.012792449162825318, -0.001373590373613169, -0.007007095923081637, -0.03944685827428085, -0.0005091156492173926, 0.05337152376109779, 0.039697081957408425, 0.018449838856499364, -0.04508781313689339, -0.013858832574870462, 0.005415728496452266, 0.02632592062055515, -0.029114246757322947, -0.010401563385573897, -0.0029195529075451004, 0.00028161002616035324, -0.027098994499639574, 0.03172762603586378, -0.003527779247911915, 0.019471299958149915, -0.024816232727499346, 0.0018712220648589017, 0.007158004971196884, 0.015227484491882902, -0.025886488842682197, -0.040668612865486675, 0.00042411322967178186, 0.0777159989716213, 0.024965915543950157, -0.03938097594170346, -0.027732538755718576, -0.047854057682998566, 0.00717345075369542, -0.009047805695755074, 0.02564486216409404, 0.035514604609403304, 0.02084798447879104, -0.002883616069738876, 0.06157781253438292, 0.025946636161611848, 0.0014373931904601335, -0.02825449410411661, 0.0006166909844726454, 0.0016093687726943537, 0.004254275264727225, -0.007880745424497312, -0.02004199445329053, 0.04950041318436611, 0.039114090675156886, 0.013454242222424844, -0.05513748483729037, 0.014126757404679946, -0.05664813472409724, -0.009968913231268355, 0.04620432192309212, 0.02468453575187692, -0.02780209085473702, -0.0018213507613078182, -0.0020059425154266564, 0.03801608834051508, 0.0004980013917431254, 0.001082008438579851, -0.008097358516223119, -0.06202995321030391, 0.022317726731082295, -0.08200789782712227, -0.01742148200200496, -0.04012884421922213, 0.017507948518929676, 0.002978493598888278, -0.06487237985138677, -0.010874031902363456, 0.03376502105364521, 0.021371053290932036, 0.005209376366107334, -0.044228234524755244, -0.004306534863846593, -0.057066111595728636, 0.021162476753615815, 0.01035263692904454, 0.043618647212459484, 0.015069798280653692, 0.01507055026057439, -0.060905961836013975, 0.01713438920293154, 0.05384725702444808, 0.08271013284380033, 0.021125491180049014, -0.02752442100389405, 0.031143239776270877, 0.0101126414749767, 0.03972619194243236, -0.025228617873733917, 0.0013458194052212366, 0.008263415714493148, 0.01175901609465494, -0.004089912206372601, -0.0034714820301167543, 0.014833175217607717, 0.006071435392688734, -0.0008935500481268122, -0.01150136661604785, -0.007742907694588099, 0.025672965135282798, -0.033838600769833076, 0.004549887375096905, 0.03934807670102093, 0.07299078399986256, -0.011843391782548143, -0.03526982058555699, -0.03409943247214581, -0.007207405110112896, 0.026651195097805253, 0.03227696887809625, 0.01833927164912518, -0.04045781706358581, 0.005568755881518875, 0.04924580610013282, 0.02796857008365239, 0.0059997740686171565, -0.00019322826844753171, -0.020446258968851196, 0.01633664896085149, -0.00015362628268627589, -0.025338370690279322, 0.01473679971289809, 0.06338015339975658, -0.003167668005836551, -0.06165138138646526, -0.026982732554275253, -0.004761265051749085, -0.026526326283721403, -0.04438894872461808, -0.03845189832541838, 0.02725200992234195, 0.00551498166885037, 0.03988326092741837, 0.013916487061636732, 0.028569531526918834, 0.03520122879195398, 0.010192908876976, -0.040672597611205875, 0.00468134051095484, 0.008253244632121832, -0.004545306542404155, -0.03898345422109324, -0.05035631441915185, 0.012060343776629361, 0.03430739274680141, 0.005791866761915628, 0.022969118631771507, -0.0009864942561430817, -0.02178858646816409, -0.012010068628441544, -0.06163260797546066, -0.01619804969744479, -0.0020918073574738363, 0.016433701078956624, 0.011717078976504522, 0.02832276624912025, 0.0006988807240361876, -0.015831833864884882, -0.00876214121895862, -0.03711761359377108, -0.018400215029760053, 0.005599645930305067, -0.0723911043948699, -0.02746306162516265, -0.058778177230337056, 0.0033918412219894525, 0.0005330619964023647, 0.047232029288959095, -0.05065305992465067, 0.005946897085424348, -0.012928521252954916, 0.017338627044350997, 0.03938320981198716, -0.011365928210874334, -0.009726529368587034, -0.0674736792891647, -0.0032329718201954266, 0.029374409831782446, 0.023629587200754497, -0.01874730057962099, -0.025182880594402147, -0.012372303760571992, 0.06442902784967194, 0.006346493767277462, 0.04683617586033295, -0.03202461654940631, -0.017113878264088694, -0.03671650576706122, 0.01202237376956167, -0.04939602478383949, -0.019308206988568402, 0.014404476435417604, -0.02663586091190031, -0.01149267544294115, 0.04649025247567275, -0.0018085959668595357, 0.022236661296801803, 0.021078835571271787, 0.09049438008321092, -0.007074706786437601, 0.03351425996686522, 0.03394685564639234, 0.007826626617548843, -0.022468709780363028, 0.018141710429147386, 0.009644904994500538, -0.022522836348968782, -0.029448905838120456, 0.004199907382985077, -0.031076224392019534, 0.0646783145997076, 0.028061121117208744, 0.0609583649819273, 0.0021254818442207725, 0.0037767992560647718, -0.027591541211479713, 0.06443495584520582, -0.024841198636762853, 0.05011472848299668, -0.0019229044768410215, 0.016171320761877387, -0.006716049609741739, -0.007482831567552859, 0.036260644986981456, 0.053850744606221405, 9.923799352532306e-06, 0.01605812543005963, -0.07016591799616181, 0.01980702504690493, -0.03154119919426457, 0.016701396030864138, 0.0016448319682060564, -0.003205943402456602, 0.016459196093437628, 0.04569859496367523, -0.001356228987568893, 0.009442616536815017, 0.041784038615530546, 0.00836756982826261, 0.021579931394124507, 0.0588355838510787, -0.022666081732441945, -0.009454672117146056, -0.012354170237541859, -0.019812793414293248, 0.021185816351940237, -0.019448125706493333, -0.0796301717309504, 0.034031017734838326, 0.006394871316948821, 0.017521037600375673, 0.029649856210110206, 0.03270804211807661, 0.015994687303658658, -0.023620672945554633, -0.011290906125520677, 0.009744194126478851, -0.01386543251747596, -0.0202568366891771, 0.007856085565997381, -0.024698493876247368, -0.025161950204079665, -0.0032278974960433743, 0.02438350670526175, 0.003874899368595818, 0.04330139324048336, 0.00617168272028034, 0.016267914481378383, 0.027717717764083515, -0.011132833226512933, 0.10254867007557639, 0.010248821610287291, -0.03564335792151485, 0.06284792057780302, -0.047097488953666014, 0.04282155442714592, -0.021880501418374228, -0.010474045748704693, 0.06732446908994924, -0.004800841108526192, 0.007285258058433858, 0.010677004557880534, 0.001166366280948919, 0.04691242635145941, -0.020502758638433122, -0.0027788273077255995, -0.01349021770647987, 0.00973216420387726, -0.005969109182157355, 0.02923057999040641, -0.016101756052658654, -0.004028265439850392, 0.02410790449727518, 0.01762981503524611, -0.013886590783579629, 0.02449022866425071, 0.010494999606798672, 0.023985772760006217, -0.009231387600158007, -0.03402133784189351, 0.016869613525958994, -0.026512159214197212, 0.009493703539623005, -0.013066266666071354, 0.006695119084088219, -0.06467070250469777, 0.0643661423779862, -0.04352782222637966, -0.05013779925478491, -0.02322885963918539, 0.03713396420659827, 0.0120754069519472, -0.002016721386123753, -0.02353230227029201, -0.03208160231179749, -0.00712704225325953, -0.030480930554216877, -0.012522343168459113, -0.030169491256530068, -0.015642484275566106, 0.025014700891328952, 0.007738604017552043, 0.021833036578273783, 0.028135381374366494, 0.06860587709342059, -0.006408941970067285, 0.06401030163112889, -0.0004870800605521874, -0.03165900351361182, 0.0027502327086944382, -0.040528239896052455, 0.041496890483408516, 0.036883555270465926, -0.020790353209543224, -0.021432515421583566, -0.01174287968297208, -0.022690217650162704, -0.006741327186429287, 0.04610077882937877, 0.03088921821003649, -0.01913864463665068, 0.02095229748637737, 0.006565294341770528, -0.006228354637671243, -0.006214538086622364, -0.024942615171306207, 0.051289684902533286, 0.03816502753718478, 0.05760440094987877, 0.018286309601458446, 0.018529519378360358, 0.01009397135347637, 0.046834061978960145, -0.019527547828133113, 0.02243628619623911, 0.02673842309757534, -0.005873869278150073, 0.00837360040894633, -0.08967316386683977, -0.013779836049590016, -0.022980775508147772, -0.009562860262367193, -0.11398416373013992, 0.00891114927376425, 0.0850063687434161, -0.04328043835311, 0.0623750393565303, -0.0040127227309493885, 0.018360176991068386, -0.05470455804132451, 0.044444331570237805, -0.01683650776534467, -0.00394809325015525, 0.0479213292456449, -0.048729587656214636, -0.002358139162004308, 0.02561712268467415, -0.026402131332487647, -0.00970011834838882, -0.023128320571032864, -0.013557870435727813, -0.03391973862735238, 0.02787356030997788, -0.032485277504591854, -0.013600369460600493, -0.014883077524253479, -0.014910066435794525, 0.06309213494405908, -0.010604617947245895, 0.03764340046710219, -0.02199703686013975, 0.015350868103338331, 0.0070637689797234966, -0.02343649999333841, -0.028173925057086074, 0.017010061101204146, 0.0018724017542062548, -0.02631381652080277, -0.004630779957576244, 0.032645120214720734, 0.013797146140973137, 0.02184585872818723, -0.021834614767993536, 0.016049032354946834, 0.004108807769577618, 0.0386766686444467, 0.002540046307033317, 0.023019338049390745, -0.03209650851429079, -0.058875230743756814, 0.02758204624336399, 0.0069245023798619925, -0.016690581712776424, -0.037503195830709726, 0.009066995005289402, -0.005831146931833118, -0.018614282000637444, 0.04740640998502062, 0.008279296649783573, 0.01045814414631888, -0.03137859977982655, 0.003652968087527508, 0.005464551358274354, 0.01038102575891178, 0.07052769123729213, -0.005119387396310706, 0.005205183208813703, 0.05114450092685202, 0.04434904254958737, 0.06312532350987357, 0.0003392857455160119, -0.010853438981438457, 0.008148675580773637, 0.04566490350558478, -0.03116038334398271, -0.06206994345722143, -0.008668031516889031, 0.010895088157816216, -0.015677404604811013, 0.022835616137928342, -0.0164000328372747, -0.03475561190607761, -0.00126564875001798, 0.02704653708768901, 5.733652616033034e-05, -0.029961064707973988, 0.04926354956163881, 0.012835273047583567, -0.028960530559075007, -0.017218808481188334, -0.03543126601385842, 0.025058521588242098, -0.006413351509198644, -0.023349866296733607, -0.04907317382993923, 0.0009760537234187374, 0.035163306157192965, 0.035436379738240716, -0.018891726730971555, -0.0015474372959675485, 0.0654865541274599, -0.023237367990458474, -0.01345510781358812, 0.056682738712721226, 0.03553206666521696, 0.044105876553650704, 0.0016925650044570895, 0.0018198296323202652, 0.025425500133249228, 0.019860769087826302, 0.015058994258508112, -0.014576573134526373, 0.015409068327189432, -0.014699398906499486, -0.016368221738489945, -0.03142883421093819, -0.03447022820029815, 0.07610453108499644, -0.022354808487134406, 0.006551517023279894, -0.005322201937107529, -0.024767228595379585, 0.03525209925216456, 0.030080966244978222, 0.02110768961242903, -0.002913393323251666, -0.02187975431978027, 0.062421859969548604, 0.02989766037390752, -0.0008405992104526686, -0.0033026881206361693, -0.006830326128608681, 0.001506177188703051, 0.022753602822516112, 0.007887739656073594, 0.02129491990363846, 0.026455811765955208, 0.024866090387367954, 0.0288739400579612, 0.03825307862749006, -0.019871926937157693, 0.030494110536742045, 0.005630616975042619, 0.032872314007896704, 0.00647203647303641, -0.015255486362334356, 0.046843004846996994, 0.013172084063438106, -0.02864149708185552, 0.011462898364200322, -0.007219882191603838, 0.04911548665386575, 0.008811744213747487, 0.013275055084206447, -0.03352403957222776, 0.008327456228500256, 0.006078235303314944, -0.01794831231105358, -0.00576333132963151, 0.02407129657767421, 0.031503489410409674, 0.02301052106722657, -0.021524502839987553, -0.0040525409667676055, 0.037323678445229796, -0.029474407744472426, 0.029150477613180684, -0.004601845092082548, 0.003492817856088592, 0.00942974439979133, 0.07026094567011501, 0.02849979882484293, -0.012336055979594534, 0.03763100215339384, -0.03894890970290488, 0.021456622889720802, -0.014465117326142595, -0.020587470707836213, -0.022110729355314968, -0.017280487313636805, 0.03902512413792471, 0.04354325969827687, -0.032581237257741126, -0.00047085088718993456, -0.008785956253302916, -0.050788259181437825, -0.03411679210397071, -0.011012214844076282, 0.001667146925729357, -0.02833101369553903, -0.03326047312048769, -0.019733727393844692, 0.018753216194611483, 0.025535956761829064, -0.021715563007980665, -0.02477857970788373, -0.0344970442495224, 0.02803237371350426, 0.03468041179065526, -0.0017807499198769952, -0.024971599562676902, 0.044925024806041866, -0.02442207161344736, 0.0025871362934163124, -0.0044871027548730276, -0.008490544047918947, -0.03044229347573858, 0.00047750773232669615, -0.037085713338368936, -0.011953386107292931, -0.031146027479113807, 0.005455267604579723, -0.04842273263146466, -0.011953926630393023, 0.06795189768379975, 0.01779214369259306, 0.0438832783886707, 0.0036041243220441208, 0.026535326525877864, 0.000982616802440336, -0.012237107073265077, 0.03587693727303116, -0.06270120413957096, 0.03895886253121002, -0.01685089235611038, 0.044476782889987895, 0.05431760596465513, -0.0011023769328763892, 0.017733627286447628, -7.512555827803639e-05, -0.0013523254118227006, -0.01014922923576144, 0.028254068698131775, 0.02235474222961226, -0.04349474690529843, -0.04836719215161379, 0.018250865564333578, 0.002451972581571338, -0.04974244692433341, -0.012578042255332067, 0.023384880181463164, -0.03207188767035513, -0.003174633666433401, -0.015110688798414295, -0.020492712329304, -0.016268135066718637, 0.014343281311475945, 0.015224317995135631, 0.09706377369873137, 0.02457404732161115, -0.017449021391997048, 0.003378747326359498, 0.060577751272601475, -0.018858242312410595, 0.036451866647877, -0.019030083622912478, -0.040669867239935376, 0.041886480127070864, -0.009765692840861168, -0.04593823860608827, 0.001331122326259888, -0.0461150077434467, -0.012452447951312479, -0.04956592184459106, 0.02276553190001352, -0.019843545451940926, -0.03479858908176015, 0.023694389137555696, -0.013120152553588236, -0.00022661931308102213, -0.030741939560518978, -0.07356639276867055, -0.017558996816046356, 0.0026831602488744753, 0.019790199099168834, 0.0043621229108062485, -0.017844499747346476, -0.0028603617638003126, 0.09173449527894775, 0.06697344037606953, 0.012182622252153302, -0.020249490360862803, 0.023938943235694534, -0.11588665465559768, -0.06152823597614375, -0.0002737529528623809, -0.00917911399296281, -0.0057674446443327625, -0.03558833505523352, 0.002427940885969546, -0.044726213040868094, -0.015347516153680817, -0.024836672225574626, 0.02633109827367052, -0.04413455435843159, -0.022537118389416775, 0.0029306775147442815, 0.03881528265429225, -0.09192108249016713, -0.01381431604977845, -0.025334045316670044, 0.012810005713568006, 0.05888291528219179, 0.05591395904331806, 0.02769419474264673], [0.009516049882591866, 0.011270882165492168, 0.005755224802814282, 0.038086882500329405, 0.04360978118644228, 0.037026560739504194, 0.041537025461763584, -0.02901454679368567, 0.02742130769106688, 0.06858064246423332, -0.014032892121022551, 0.018720906333509667, 0.04505991025330945, -0.07544036638201924, 0.0546903459243911, 0.019836719537092123, 0.029092720523592165, 0.00904456008929253, -0.020049954330791504, 0.02337262534067734, 0.04456006178164656, -0.01204829244816553, -0.04443353929332178, 0.03875950802846993, 0.008747558371410369, -0.04734069687827737, -0.05169447908301435, -0.05063897426030002, 0.021302119496367185, 0.007304907483649113, -0.00010175717411886869, 0.022365588412505917, -0.01563230716390086, 0.06222605297325308, -0.027329548003989956, -0.029388366643094868, -0.0356724123243132, 0.016305614698551397, 0.011765490986733637, -0.019396988998819985, -0.03424650781735915, -0.030810253779451725, 0.008480278339378932, 0.05595468245963176, -0.02881158796041299, -0.022963300620275677, -0.015041521757487518, -0.04507074448886113, 0.020727750981744834, 0.001007217068685241, 0.03545137384220444, 0.025638056461212255, -0.05317428089178335, -0.016178296902127756, 0.007736021898684868, 0.011730435322119911, -0.0171430065517955, -0.045637376743902905, 0.006948209144496117, -0.0024729324828220074, -0.022733375361120012, -0.0413154616026171, -0.03905158172128443, 0.010309691030651918, 0.031655109892503336, -0.023759051222843712, 0.07038769469951515, -0.010889605306121012, -0.0077306619217291025, 0.019714939638394793, 0.03193738298250954, -0.0013061626294413858, 0.010218240413605055, -0.013751972317327069, -0.02290906902391359, -0.04775307854067788, -0.013601924067154932, -0.022100739035443344, 0.005670178097587903, -0.1002602775757074, 0.03206271057920747, -0.0021390134095314347, -0.00033092546384027753, 0.06281625482591686, 0.028411765855806008, 0.06845219721791189, 0.0012289194483043357, 0.025236801374242293, 0.06566917622450169, 0.041987420793791475, -0.0025611930629433205, 0.026246415797216816, -0.012343183545824817, 0.057204712566301635, 0.06244445312036536, -0.0051762653456335714, 0.06703116343404497, -0.02708915637898738, 0.0022203617400105534, 0.01916208246233176, -0.0027421983932087676, -0.01875905366571392, 0.005262016693130449, -0.010792900939413024, -0.018240985315421964, -0.05422533213085735, -0.016666183827612466, -0.023928461446388872, -0.06431218226120225, 0.04766035833438348, 0.047962386561957476, 0.011465233398842596, 0.036878401902691565, 0.04337175450322936, 0.006954230268367565, -0.03902655206560989, 0.000294608468338436, -0.001994881894261849, -0.05241521884707044, -0.012833340936559377, 0.01556514060715666, 0.047518034016842535, -0.005104158125421622, -0.0070068168237494635, 0.06149760880873547, -0.08890782147902879, -0.05877219431524335, -0.002831197309533663, -0.03818744807408743, 0.01605846554249961, -0.003507144228102669, -0.03317496980108166, -0.021694773282553363, 0.014349703271624277, -0.027575874030695224, 0.0467300203205016, 0.01923775003866318, -0.0023805347236419173, -0.05026183168808286, -0.012183791556951573, 0.004470507010191367, 0.00673329228429396, 0.04877481630813481, 0.010337444961826217, -0.0305352691437624, 0.03802914749967627, 0.0675337734609758, -0.04845506320876044, 0.02610533883495148, 0.014326312560393106, 0.02723669902470397, -0.004297086896410819, 0.045607942344888186, 0.006142934238864934, 0.010938986160357782, -0.0351481626222275, 0.009899966900868442, 0.04660042549347737, 0.02251326666281402, 0.04084672435664158, -0.015754799733090727, 0.013152218169783597, 0.029476371043431766, 0.0031416899138384663, 0.048850595976872815, 0.019749068954397535, -0.004743089845778345, -0.03374902187248212, 0.03839570157955862, 0.020282895405046675, 0.0011711710507039685, 0.006569074617460771, -0.0748429584834151, 0.01966313423325928, -0.05042736282549319, -0.009889536279218805, -0.0060050338968840496, -0.056161783921548594, 0.0400220016004065, -0.009425625205867028, 0.01405186447914036, -0.024590168176902107, 0.02938179183732718, 0.029218272535295808, 0.053747918351938644, -0.03204127614702221, 0.00787831801944418, -0.04752651968580702, -0.009595717945931191, 0.0350419436645203, -0.011257820394421518, 0.0244039301771298, 0.02763160186684281, -0.028609726637397803, 0.004890929623028499, -0.021121249035766787, 0.029863291234325115, -0.017355343519603987, -0.01300668429145254, -0.017886094520557748, -0.02289592434656055, -0.010165290716454754, -0.019058809007976907, 0.02109963677944117, -0.031035385263621162, -0.059471893218604244, -0.012835279504272278, -0.017222096449743894, 0.00625463670252021, 0.051563279042557454, 0.00358787262706934, -0.002461087829834275, -0.02645606229648048, 0.024695244066576482, -0.022313831135526985, -0.041820876677098315, -0.05255692189808385, -0.015323759543567034, 0.01429029050770461, -0.040379336091247456, -0.021354894394121998, -0.06968426588018017, 0.012916249691129686, -0.006360629249475997, 0.001311526187860233, 0.002910418884770446, -0.004903327516983965, -0.036438185038934714, -0.03687608034592106, -0.05271179799698297, -0.03795652860141478, -0.03322237977268928, -0.016944560112978124, 0.0183132334672385, -0.0033549279738928454, -0.033812625612531566, 0.006468246583653525, -0.004055745074087102, 0.0326761702423984, 0.00641545863409829, 0.013640092937713531, 0.028638188554953675, -0.030997822268992774, 0.030945689142153628, 0.019671544574857557, 0.032544700442784215, 0.06566426668538822, -0.019545214732948395, 0.004630904253885263, -0.03731110137521118, 0.0026790464895055655, -0.009849735029195738, 0.0007571074911268821, -0.012208957514111038, -0.0030919053199095292, 0.007454770908970091, -0.03461819150462167, 0.03270874704658492, 0.06533930721738766, 0.07027517117422014, -0.008296841793133902, -0.04991829284977507, -0.032556839279790376, 0.003462827929536576, -0.03449107886224746, 0.0053409720232195975, 0.0483803698420458, -0.01407850457642464, -0.03828494115327271, -0.03243097475700491, -0.022435953629379765, -0.01583104581718727, -0.02702462978284103, 0.00126228156515046, 0.035338573830283805, 0.009071948210610458, 0.0017185555917216258, 0.049366304990397104, 0.03692089537200344, 0.022904348665814575, 0.007525767881665222, -0.010976555327616294, 0.0449760516351867, 0.008485317100843858, 0.034935030215658816, -0.0059590413717917155, -0.06539071289586901, 0.002277403732802488, -0.025550347138756646, 0.00362115742358278, -0.0396348454945908, -0.02701737088271578, 0.03732605178767663, -0.08172470454931646, -0.0461817787159893, -0.047500333293744146, -0.03060053828668111, -0.06556791029522213, -0.008549973717428171, -0.015009493223086453, -0.04241736666719109, -0.011045419957848619, -0.025457350797823717, 0.005932910025058697, 0.06115410919304114, -0.031225433101685303, -0.05098561666925154, -0.0073034876196750205, 0.017857437507746203, 0.022308483951288614, 0.017307919397921437, -0.059650200144421246, 0.042773323995486695, -0.019308423725924194, 0.03152726281766622, -0.012439638403179572, 0.0007381371368083722, -0.0003458019123511111, 0.0539661512035533, -0.0022152790407308533, -0.03230638887976283, -0.05921915133042524, 0.026719867552056957, 0.016542706239852108, 0.015678804218799178, 0.0672674387532612, -0.05755895469068846, 0.003358217990099616, 0.034362782966452385, 0.0355071383134917, 0.04493934314611052, 0.007936677930715575, 0.009061743357316104, -0.033555062403889196, 0.04144285982383377, -0.03513182725683788, -0.03544443072273143, 0.006958405064348437, -0.02307103806494013, 0.030360438305277994, 0.04768057892396886, -0.01091501281651765, 0.016208857813825486, 0.01790556745393261, 0.04401849837816101, 0.0027365630375850956, 0.0237978355142796, 0.028423212996301413, -0.05415020903805167, -0.03534140182778365, -0.02144611800968118, -0.028873958259107223, 0.025500144428714573, -0.07259792713174756, 0.0017205447392073178, 0.050735037028034495, 0.03732375889226554, 0.013283132290602575, 0.018873063589000285, 0.004814453427625012, -0.020474236000742364, 0.02311145010741729, 0.019047909532409542, -0.008195878978782675, -0.03718993174567291, -0.023119291694193658, 0.03250832371270079, 0.07119164601943147, -0.014333631375893503, -0.018842065662307034, -0.031166501463924717, -0.011430653617236889, 0.010573005669156874, 0.03772291458710397, -0.003004602476583367, 0.0034416543960523946, -0.001568580629356014, -0.047112740198009824, 0.012565005946894013, 0.00886425922844861, -0.0007440052464325886, -0.0475257881880785, -0.042895356692472714, -0.009236863784022154, -0.04596132199334461, -0.02582087237145114, 0.008532140750535978, -0.02390104247811871, 0.029782529467781975, -0.021804297487257766, -0.041494276910578116, 0.0284512835696943, 0.008513540643207881, 0.00345912339570904, -0.038129955265496104, 0.021622694180949063, -0.020616777304124714, 0.060377749804009936, -0.009241967726873872, 0.018759938923309474, 0.00877367525598461, 0.008100905628905582, -0.044842693471081094, 0.0015986906063471366, 0.028216448992338973, 0.014647344957640172, 0.016837377754324902, 0.032908844911606346, 0.010509791686395523, 0.02301562693633532, -0.008325302158881314, -0.02303893165750994, 0.06112316185060589, -0.019375999987049773, 0.02444621869717235, 0.002119339492748739, 0.0025483516918766887, 0.01351243173290235, 0.0025385324898173985, -0.022026729859527767, 0.029893817866870846, 0.033455574594871945, -0.004822376841021362, -0.03406554227369611, 0.002049751605095483, -0.031852800871107714, 0.02632690899256264, 0.06800731725955711, 0.020204645901294414, -0.014224128255572463, -0.08949207536391135, 0.006582380388858755, -0.08188775182270247, 0.015071677135975351, -0.006611578960136543, 0.03609584185809034, -0.007529754163971843, -0.003994883596204817, 0.006775153322926781, 0.09264514665043687, -0.0020274588891776423, -0.0559702653074889, 0.025477922824638843, -0.011019127198717907, 0.02827791622483865, -0.000168242729744833, -0.033465026018008076, 0.03548113078703895, -0.026352782045897027, -0.038841624728063615, 0.030800602843641677, 0.05552793829678691, 0.035124500019102486, 0.01899323350216279, 0.02042257788669296, -0.017907829099844057, 0.025154441658912587, 0.014001932733061849, 0.026616666553835298, 0.026873865566616514, -0.03152991047767887, -0.05065974501507032, 0.034586618570019545, 0.055277211060307724, -0.05295561822011517, -0.0060415620986245325, -0.019447824880427747, -0.004362020445569032, 0.02844050762630935, 0.001964978781183154, 0.005193780817318553, -0.002766318864537414, -0.060986302096787535, -0.024380477282433283, 0.06609016661250756, 0.01024011799593915, -0.01897706440653322, -0.001860526927925014, -0.0156719361897453, 0.00046521008853636636, 0.027646252434194822, -0.021094114643289072, -0.011581347131455703, 0.04384521615053609, -0.017491360208885698, -0.04470045355032693, -0.08049985901980344, -0.01299465408963094, -0.0239971965538942, 0.023561521877691855, 0.009624284506800963, 0.014614393561455882, 0.008945543737091385, -0.03874558797110843, 0.037942999169733004, 0.03657522999306445, 0.030807949265342256, 0.02420625179217579, 0.004342551006841608, 0.00348714481803368, -0.027281886066179126, 0.031287945219300485, -0.044491965307265326, 0.04045050364756625, 0.006865174781231446, -0.0077261961637190985, -0.054357863056181906, 0.01040281310609207, 0.030021305834997642, 0.04097239496314419, 0.009999860578879983, -0.01937395473190954, 0.03884768247320243, 0.007622188392080214, 0.008303034200425486, -0.014895555836582102, 0.006995044277560439, -0.02775017841811886, 0.0029468063992949475, 0.021947386577700966, -0.04671642039525364, 0.03582361460594961, -0.011680743629269446, 0.04896958844251106, -0.006577935406504403, 0.04182038415933406, 0.008349263238979027, 0.013625194376729216, -0.014793537431832754, 0.010531214961052467, -0.05405378525968018, 0.009390361241981282, -0.026531929746959573, -0.04799978789940294, -0.020756168909876516, -0.0025347284413171425, -0.013019113473728791, 0.015834227103941, -0.0262962018222829, 0.03734388910120512, -0.02463124674027526, -0.046966857771609964, -0.002184153715272138, 0.01869970946916851, -0.006528368239807866, -0.029780171456889604, 0.05420209237873458, -0.01898654289436391, -0.027130632471517785, 0.018196798738856594, 0.01366091667931339, 0.02880812939224304, -0.0014270967427545203, -0.001679539856873448, -0.02461009554990062, 0.024735236933347733, -0.03624104197012949, -0.00025183050481937183, -0.04207747501826864, -0.03441747261707521, 0.01576111413252653, -0.01920997523970805, -0.0044733672938141525, 0.013306321951204843, -0.06851056307615812, -0.02683108029576584, 0.038789592431656736, 0.01582679664047952, -0.00997093824715558, 0.024159252344848205, -0.03717371605130737, 0.021677342771823867, 0.018114562598336336, 0.03812644985503967, -0.05617450116882642, -0.02928311241830864, -0.01150718291513602, 0.0010142262505906546, 0.03523274197821232, 0.015959175807267276, -0.029711666858490714, 0.01129613960867348, -0.007406711740157532, 0.036586867136381325, -0.020114011331918938, 0.0004079574388599072, 0.004073769677464872, -0.0019173693047724732, 0.027707739633925563, 0.012649438253834015, 0.020864275805071726, 0.0239474502400684, 0.010613497346631938, 0.009344690455557685, -0.000523540461158812, 0.03360515338512904, -0.006655612235262082, -0.012799597037101583, -0.049136959139625944, 0.053601968419029235, -0.050120996782071534, 0.04821101417454661, 0.0360324995863493, 0.022063041831051827, 0.04816567436788465, 0.01015388824926463, 0.004253323450471247, 0.0617711172797525, 0.02861993052008087, 0.018200516708531542, -0.03942680688307368, -0.07130536598981708, 0.04727384034635568, 0.02668914256816046, -0.0053651692855075615, -0.010047457066019921, 0.019739823993145566, 0.05816662615749614, -0.036385644763250305, -0.013285728347845674, 0.05218894276377892, -0.0011164015814513276, 0.019903986710914492, -0.034694998340364325, -0.06210132630543276, 0.013816357164018503, 0.008334510929570294, 0.001748802509812089, 0.028846335228029228, -0.05029633499809182, -0.04810656133998551, -0.0010317577870101874, -0.0017075612462518937, 0.014861720670416434, 0.02810017566575656, 0.0012457667208948184, -0.007332599225773206, 0.02739440365436982, -0.018341008319856918, 0.014083481692745927, 0.015875353709619303, -0.007481185841994569, 0.018128349793970662, -0.027726787693975595, -0.01054967318029083, -0.07068219264400566, 0.021648974145375352, 0.008507417721293167, 0.0014295702467999362, 0.04204188510335237, -0.04230825954899063, -0.01835439368063128, 0.008455172667196743, -0.0380749869685971, -0.01712216787239499, -0.009813621948649766, -0.019409745276690593, -0.055459423215736645, -0.014843167563047665, 0.04520304081380705, 0.02197607252015994, 0.0011231174155678023, -0.010340458518241407, -0.0434493986853458, -0.01304053041365056, -0.01471649496401508, -0.033147991520015505, -0.04281483129442174, 0.019705712502441866, 0.004406636000933235, -0.0169762284698102, 0.0053178367179047056, -0.015863145780902952, 0.0286879055326748, -0.03738908914739223, 0.020181137328069712, -0.012776354184951341, -0.025982685274757816, -0.05914919675780559, 0.00023097761197138703, -0.0038091319572657175, 0.006262176394882784, 0.008862874077917918, 0.004868766408947785, -0.003557216503538907, -0.003715148214592956, 0.02566437198744319, -0.024235881378884345, -0.012917676655380636, -0.0036281196774270424, 0.007999271276358758, 0.02698948536167617, 0.05424465926713866, -0.011284102580579781, -0.003641951883713617, -0.004629355367941155, -0.009653117278233621, -0.024514340307839506, 0.01834854666697951, 0.02043310674849112, -0.02513065585221953, 0.04372206260427646, 0.049624966551558236, -0.005199897580929235, -0.017890420552126175, -0.019206574168093408, 0.0030648529471394327, 0.02058065835129867, -0.030724912647133434, 0.03092862558707986, 0.024342780689482858, 0.047299730230894746, 0.01831510251107875, -0.013711705795225576, -0.007541512635956576, 0.008586886538932297, 0.026750497512704446, 0.023968699862481076, 0.006805162278230605, 0.00934427598238642, -0.016998743997306415, -0.03290140586482559, -0.04742060821610957, 0.023361117341453942, -0.0012595776184091945, 0.01119487449454285, 0.008193699920037007, -0.032843426788169566, -0.047605507900628605, -0.021498532291985974, 0.024511928056186898, 0.03224016096024176, -0.006227985107538546, 0.007058516546317327, 0.03680486938388352, 0.014044605702258066, 0.03807287855856642, 0.003956823287422684, 0.065703488168308, -0.04487500625250487, 0.039682822993847566, -0.04354450624926689, -0.03256699588588159, -0.03879041487437371, 0.05371372831486175, 0.022526442564747597, 0.03569412788535292, 0.06220008283790959, 0.014997707351172599, 0.03494961403770499, -0.01516648318192175, -0.002338864768328699, -0.008781301875543887, -0.03758427336036445, 0.003715915538663574, 0.010886511969338366, 0.040696491029115975, 0.001687543615528113, 0.03507884570942956, 0.03223690049573516, 0.0073287815323526285, 0.04849223882388844, 0.018455283003082936, 0.015962791406517428, 0.0028160630101366434, 0.03142912691771227, 0.04720391253544792, 0.0025100922465844614, -0.027319156716268563, -0.04171902416242494, -0.01685308583504785, -0.015696129384114722, -0.06826652724066973, -0.015354651267776532, -0.025692113214828532, 0.022174311509144336, -0.007166841827231246, 0.012642867305735772, -0.020208257767634796, -0.005402040593051592, 0.055112681712298744, 0.008395037376849329, 0.01432150545577309, -0.06752393297163321, 0.002842468550584146, 0.01153918712388551, -0.01869216672910948, 0.06350906552378599, 0.00709422567766733, 0.011497921503486339, 0.0504130905805533, -0.04751715295110935, 0.07702783322003247, -0.007067765433785956, -0.024453338185230766, 0.021345815377540645, -0.0018307302223449238, 0.04336413741747337, -0.01272947110142811, -0.04453606671723965, 0.031220943627811665, 0.012339166749982363, -0.03305348629443473, 0.0012493725590923353, 0.04074988418766244, 0.02826432074335499, -0.004126757324368526, -0.007469153444673955, -0.0094296910640517, 0.024628191876478616, -0.014999053282355502, -0.0028645915135510685, -0.0644280145951182, 0.04904834827571126, -0.010643444595253305, 0.006330692622061935, -0.019573151830719098, 0.054242449206256156, -0.023042719440413535, 0.02783495598653413, 0.031538750777522634, 0.011656567812988742, 0.0019142457513541397, 0.01735221650785583, 0.016608040411316194, 0.02311838770208547, 0.040185989495019094, 0.0023849449807477052, -0.0365772740389157, -0.0029118359995356485, 0.01202980160932982, 0.021938671287615934, -0.01249715134303568, -0.03698268206543328, -0.021061900938741565, -0.005919675961174133, 0.0663165271817281, 0.03396181417452065, -0.045994670520614976, 0.014605797267453577, 0.011199659835821241, -0.06847108590996862, -0.015828974291595244, 0.0620689419567265, 0.013556318509441409, -0.015797176541286973, -0.002014999731059139, -0.07621272531588183, 0.012723692523384535, -0.034597659278167124, 0.013067906589302484, 0.07571770308145567, 0.013092448241405551, -0.05899354771871011, 0.010775335801328789, -0.051589302538926636, -0.03445031958562818, -0.03785607119706137, 0.03829250473380152, -0.011263760984792972, -0.016789113959259597, 0.00042935685994050074, -0.061731723309633224, 0.05050919671670721, -0.011720835008833148, 0.04489576826471014, -0.0012128147860225257, 0.01435636097560281, 0.009910018797270954, 0.01755978647764057, 0.014710611519000348, -0.019712839272029836, -0.03334894497568854, 0.01210975659843381, 0.04946923930933337, -0.028551090438595574, -0.02333792934785377, -0.004124709206791784, -0.0015746785357535777, 0.0025611494217395274, -0.002380494283521806, 0.06039225240879398, 0.03436894203934964, 0.009354021670408852, -0.03291689520637641, 0.028739736284164374, 0.012185235806221398, -0.030564253005467303, -0.009843919502862552, 0.05787936890642864, -0.007180413418476864, 0.02595258261992725, -0.016230663609899523, 0.01951350250249308, -0.004529743796974468, 0.01176688496008915, 0.000342476353409396, 0.02313561140453899, -0.016505234060146524, -0.04684446654579654, 0.026384734132651955, 0.010468773100253763, 0.03507640643871155, 0.04546445099978065, -0.006146487335865402, -0.027376008702462436, -0.027257501555374368, -0.03377601428492627, -0.02553669895991286, 0.02298904788868921, -0.002443145044382015, 0.0406945589783195, 0.010007409400019103, 0.03065369757805901, -0.04925260727724372, 0.018872323253271758, 0.0358632195962372, 0.030621000249239985, 0.019965283568230505, -0.029399732933085405, -0.002574394392626541, 0.025389389612337956, -0.044138224943386505, 0.02210105327677041, 0.003787556585600839, -0.03597911089660618, -0.0633396571900045, -0.03717975247523193, -0.005133990853988239, 0.029283277780705837, 0.004657298776905185, -0.0459559349329163, -0.06554142970927097, -0.019211972371373245, -0.03674630972771923, -0.002545559910904994, -0.021066151633128503, 0.03711382874514626, 0.044867446018608585, 0.02708872650365533, -0.014368843189114864, -0.019435925427903622, -0.004413108517564103, -0.04802632464862949, 0.0031976683984852447, 0.015898070292449513, 0.02930395910582276, -0.029131307942325615, 0.03535107806313236, 0.010678055638148689, 0.0349026133686988, 0.029527790418742792, -0.00039695428219191516, 0.02096372745296973, 0.030978679006286963, -0.029184958813852836, -0.023999975712299445, -0.003332092592378715, 0.00953179541236387, -0.017149254005119956, -0.004361511009550898, -0.0022212479827114257, 0.045232644125661865, -0.030689439136230828, -0.005851839977546687, -0.005281274229771301, -0.020747285733446304, 0.04380477794498794, 0.04566712313294495, 0.036469581165512126, -0.0036727686618696865, 0.0174012067660681, -0.041982758043238894, 0.032380389924196465, 0.0012495768483914754, -0.033510896676256605, -0.00269154200717851, -0.010395777823857992, 0.0035676432418514964, 0.033870894067223385, -0.003781973205081309, 0.05479745141467656, 0.0400063074193703, -0.029796240104578913, -0.03246720634783999, 0.04419522744962531, 0.04448458065993023, 0.04421474432671862, 0.04325829299780613, 0.08972806095590945, 0.006775541595311193, 0.06927678892213267, -0.026104054942017316, -0.05800996881744175, 0.03584202437869626, -0.01274740009332542, 0.02056992125472633, -0.06653961238029434, -0.015140955063536105, -0.060740601694889776], [-0.012321477003911115, -0.04413031116132265, 0.06425330864025, -0.004742012307094805, 0.008284475890231156, 0.022504887864775093, 0.0016305602413191848, -0.010226654633116151, -0.026052298646134113, -0.007847865673673047, 0.0009142245479507767, -0.04503031990515897, -0.01136395487557833, 0.039131808304570546, 0.046193401055741753, -0.010246086299225854, 0.02238735464104269, -0.005800463375319592, 0.018251924969607047, 0.050279156538862814, -0.07341446866137422, -0.036697551216881175, -0.00654075367532516, -0.02517193772919945, -0.03990218262090159, 0.016274702162342766, 0.018907027839446297, -0.05734161923330265, -0.027595629547145735, -0.028563076021767348, -0.00879611033995293, -0.036175417628283094, 0.017767510286370875, 0.01751599610914197, -0.01683083229400944, -0.022485051875875354, -0.016278594292674607, 0.02180981882989987, -0.041769997667236346, 0.038023409454162105, 0.021040879250135, 0.006847551412868615, 0.023081450328091765, -0.01267118479543175, 0.03999559846709981, -0.009080718591910942, 0.03305907869602319, 0.018926233309054137, -0.01678862199168888, -0.003091040048724839, -0.029673404310946686, -0.013213418719197428, -0.026992053387202647, 0.012923497805616453, -0.007797610260936467, -0.0029156373309935984, -0.0038889184241222233, 0.025883829121842016, -0.005498694001112862, 0.04079673198190572, -0.0474225508464432, -0.028642658257140418, 0.03963961392739989, 0.034965553865050784, 0.004714858841763397, 0.03389577799981075, 0.038679312177106705, -0.018662915719066094, 0.05897945407813551, -0.019958854786490517, -0.03372417795300406, -0.039724421617840996, -0.008219642490505889, -0.018437859123937653, -0.01673957457907053, -0.012140217264781202, 0.0037777447449021395, -0.03653368051324232, -0.03220366587756552, -0.006202506350692489, -0.006811642785994486, -0.007234676625900025, 0.0402577834024294, 0.022574537589497963, -0.03729056005560544, 0.051700294226874235, -0.022317950997425375, -0.00527751695049275, 0.008465394797993232, -0.012850961544189205, 0.008006793151859853, 0.005279495377824427, -0.004958258847528446, 0.015959221649083866, 0.038594030426199494, 0.022656444176778444, 0.04111414699751315, 0.032742918015643295, 0.011154603831996273, -0.025495162395221264, -0.017619134355473073, -0.020796087445721916, 0.08004487563915176, 0.03784563705964048, 0.028489309628428328, 0.031165297983410046, -0.02918698333398946, 0.003977716476896369, 0.017841276395704243, -0.0310573752595941, -0.019564027615840803, -0.02706394720134822, -0.019523679556548726, -0.013624092901740114, 0.024148867652371987, 0.0267636124854728, 0.024964687824102716, -0.0132126626775016, -0.00971956257471343, -0.0075994791498099346, -0.002398806361213357, 0.0267229809368301, -0.022854428823208827, -0.01003919585039542, -0.019657312736036693, 0.026428299990431944, -0.011343509786799835, 0.0022569792840749447, -0.01970092998964399, 0.012524042540579837, -0.01433006339982441, -0.0007965200943410612, 0.016609657685707706, 0.0178958990901852, 0.048600057386237336, 0.040226917171949025, -0.00493019667724242, 0.0011284049537454831, 0.020693194754120167, 0.002063198048146395, 0.018563498351741595, 0.041033549892723876, 0.04363194273498563, -0.004560652398214657, 0.026497164489500204, 0.009481758308118633, 0.02295403727620638, -0.007183725355473189, -0.0391609467679882, -0.004744803678595538, -0.03711833785320388, -0.012430824379161905, 0.07258757180515651, 0.03916481242650281, -0.0030677836217066715, 0.0409494250082881, -0.010281683456200105, 0.015649732406313028, -0.034066368008525416, 0.013895221041605947, 0.03050887225911681, -0.06701256993335489, 0.031102337222372073, 0.04245722036458319, -0.0057521940570249026, 0.015641678854414368, -0.03274739776173814, 0.0016298537260670577, -0.005332780786204098, -0.025314436471027966, 0.03108994074615467, 0.014682074855797308, 0.04134019858911943, -0.011168537980629877, 0.01913861998777367, -0.03669372538003931, -0.01429519756258439, -0.020764889883989526, -0.003966481880319041, -0.024674683301266202, -0.00957952246702886, 0.03367514267405009, 0.011470386066201578, 0.013517207824223523, 0.056246598572749704, -0.08702500162936062, 0.029048669817164783, -0.10388103756316021, -0.007049703916847991, -0.05142953514835232, -0.006985958330995074, -0.008723475801863993, 0.03759382067671664, -0.017786901483601767, -0.0032523073017440597, 0.029091399581202125, 0.023525974251075603, -0.04633659977659325, 0.05657901217447688, 0.010069470532687431, -0.007023523313581978, -0.0016599263203662118, 0.04595661876549673, -0.018503447835479095, 0.0033362527911527766, -0.014966773241310377, -0.04122605754853287, 0.03933796358767222, -0.033206532410736705, -0.041959280817113385, -0.021329413415370226, -0.017521513290647807, 0.01434095762521693, -0.032006079492094934, -0.017042240517744656, 0.0712059044316805, 0.028495065162213562, -0.005838373771562422, -0.00765692854294284, -0.026752656985854394, -0.04758278069014494, 0.0098583638439878, -0.06105928191524046, 0.022867318209341903, 0.0049652011614257485, -0.027380743660640004, -0.002734112760854941, -0.007159311032794028, 0.008804483439356796, -0.005547039529220903, 0.03901482969728284, -0.016097737909970437, -0.02016467934464273, 0.028218104169974476, 0.04893811764941958, 0.009524993527700906, 0.007342438364066129, -0.01666461758309446, -0.04919169808501488, -0.004793072293575767, -0.01972147700206757, 0.016819780175582923, -0.05791833444226621, 0.04852395178080907, -0.0009605536573555583, -0.009928477651240261, 0.009546000931356772, -0.02342129746093428, -0.047070502922816226, 0.0221658829555601, -0.021787010709776622, -0.035179812449081485, -0.00567804353580296, 0.04435183884436027, -0.08306092802711214, 0.010773084778549737, 0.0016082319995565971, -0.026845182949416048, 0.008288815225746572, -0.004672081721995294, 0.020453466837101378, -0.018929208764488545, 0.02778512988235147, -0.0005637456498233575, -0.014800087721236088, -0.0023141431528348657, -0.00028686347061224887, 0.005366846854952232, 0.03410999204779357, 0.006763585737004249, 0.02296752217412526, 0.04875108287743138, 0.005980248541738774, -0.009876625905656605, -0.059440143929157, -0.010210857533219465, -0.0038531684755911608, 0.006969665897826806, 0.02102268231382935, 0.0020531040062701884, 0.018409705737417473, -0.01654244288246044, 0.08276783620046513, -0.023617327831305615, 0.039131321078239314, 0.016219501202462856, -0.030625991989478605, 0.04687916038301372, -0.00884754247196973, -0.028053988671785324, -0.013362726890714575, -0.015472481042947834, -0.0021146596002102266, -0.03188998700952021, -0.027833029384542518, 0.0006823500993225649, 0.0038123850569078003, 0.013611592794068319, -0.003131132235165474, 0.03435658079257172, -0.03590666271784018, 0.043749013154003286, -0.0006360473927657668, -0.008361523411017105, -0.03021492133463236, 0.03699718916604012, 0.0017924688004619157, -0.05066961004057925, 0.033790069496250526, -0.011047266734017364, -0.013130071895965626, 0.001105012915359558, -0.0199030729010365, -0.013299584936067529, 0.01764221093871615, -0.004421411680688084, -0.05693246568586324, -0.04830797452533235, 0.019331989179532546, -0.01192916288113866, -0.015005205716658365, 0.021382579677842833, 0.009242701662906496, -0.041402445108580266, 0.008428926978108691, 0.0838120102077084, -0.005705817774011744, 0.0073850086653852245, -0.0113377545823778, 0.00017138278885849706, 0.019381212339976667, -0.032921537277033786, -0.026406365147128175, 0.025532083454973466, 0.019697988318798536, -0.03281262444529575, 0.048882800340803896, 0.017616873281344515, 0.003174444478866726, 0.03579550431586741, -0.008735512744024057, -0.01473931485038947, 0.01956290194664728, -0.028139129777953888, 0.008092171707496331, 0.011584670612964348, -0.024221649348240518, -0.008343961130266274, 0.0538674813937436, -0.08256882636526683, 0.015540966666820775, 0.021208621382791615, 0.026801900882296486, -0.009414446555088503, -0.00014580212483212077, 0.008444334286986054, 0.010665806910410736, -0.016170570692296674, -0.002929868598067757, 0.03808910279191046, -0.013777195688774606, 0.026791556770119157, 0.03320966339524997, 0.06016953556645488, 0.05493441822159227, -0.03167025750099588, 0.01419430131268081, -0.00041983615526883754, 0.03271477184733205, 0.024305068267058687, -0.039747846923149074, -0.02510966749670125, 0.04805649388184676, 0.012314843025875585, 0.01953990966351668, 0.02609900994617039, -0.030090694311863998, 0.0005014090300462719, 0.01204098482972313, -0.010319606585425628, 0.00445672171984407, 0.046976031867509246, 0.07309933184375793, 0.04901896075489735, -0.00973472021254686, 0.0074054961854960865, -0.02671300269122857, -0.01949889860736243, 0.005920583850026362, -0.03773450231270169, -0.018174465808880436, 0.0005011329001242074, -0.0005635755203653899, 0.014871638874087743, -0.04847272608237441, 0.01931584208427971, 0.01886363687354125, 0.01134412797973743, -0.05964351215201022, -0.02396213858496561, -0.024755937069684337, -0.008076024895602125, -0.05198735691340931, 0.017405993654117706, -0.060238051649287194, 0.018937763697810188, 0.014347192862776637, -0.04313269101957638, 0.06295729944979903, -0.023571534430259132, 0.01767615260319929, -0.007966523930216672, -0.02447258515416725, -0.020262485670629854, 0.08265696174057971, 0.013591876469971245, -0.02689071704228508, -0.008734687738425681, 0.050824042734125566, 0.032214102490172675, -0.0017945372426729032, 0.049589605458748506, -0.036818011521921636, 0.047734113670432496, -0.008900021081364673, 0.008666979173477592, -0.028012244376312414, -0.03180450860442914, -0.031622045964708786, 0.017365905455995305, -0.01199881628744751, -0.01177781759482354, -0.0014227934941301048, 0.018989405961061058, -0.016305364235450905, -0.07158208740058086, 0.031071873112562133, -0.025225402272725097, 0.07571395144308771, 0.014704073055642785, -0.039300424550332294, 0.0651230597448005, -0.023176132806488982, 0.001547516248737106, -0.002137275302003373, 0.08116740469755046, 0.033328145481795204, 0.04207188272906986, -0.034498292573474795, 0.04688976726403298, 0.0334611127604035, 0.003828659698939564, -0.0389146075010309, -0.025739861694708075, -0.016425652450336226, 0.050409898341778586, -0.014305864163346236, 0.03726212555834494, -0.005752978325284017, -0.0338189548876473, 0.020160283185658753, -0.019762342307739537, 0.037767340403386127, 0.024707348480998537, -0.04077464171052149, -0.08187558830965055, 0.03211218126110826, 0.04243398265848522, -0.006645973404453028, 0.009859997848047442, -0.021470101922649677, 0.039803145603061134, -0.024254350048571044, -0.03428033473019948, 0.01646532592872607, 0.0027846774183492217, -0.03887113844100982, -0.057032959200178854, -0.015950065528669906, 0.05205769815918965, -0.01292028454929062, 0.03780495453129945, 0.018796827282899643, -0.046039273074785704, 0.07375245002112715, 0.023010590847766542, 0.039081480890558906, 0.008699554281772763, -0.00299738048031083, 0.03973909586232062, -0.015611947838909119, 0.0005798469577145936, 0.018986849908879402, 0.024801109152343178, 0.01143492267518738, 0.013049837914523327, -0.010511513758325985, 0.025837741875458085, -0.007396520096636935, 0.038351750661789474, 0.021685200244584445, 0.054072269779258386, -0.01084720976270739, -0.01745166616160291, 0.00013423233275875954, -0.015084131974661385, 0.005132741081039125, 0.035796323310656214, -0.05262893884210083, 0.002211716335067224, -0.0038784754862870707, 0.0076664399555346, -0.01188297518697283, -0.014637416093190893, 0.019749589517702327, -0.010394695967961613, 0.010098196489055232, -0.008286478594841426, 0.023325185633040528, -0.0010844850061213617, -0.007253680352688958, -0.04267886465925943, 0.0078942730557537, 0.011294233736272766, 0.03922822704743732, 0.06337289310895457, 0.04002610050251712, 0.00989737938396906, 0.046814930011096344, 0.034488886050149846, -0.07813384387700655, -0.05323882437881623, 0.014130409010893515, 0.04746671018468859, 0.05195413487434938, 0.00822891353848469, -0.004113471383196066, 0.016407243803448816, 0.00766110191586571, 0.008004494307516267, -0.03560658986655711, -0.09079545082491955, -0.0077662580144861595, -0.00799465531492629, 0.012459542145440445, 0.010960568463029719, -0.01743558536932137, 0.03610046988339767, 0.004647270876536987, -0.03362555327620463, 0.016856591327821562, -0.03713352415516035, 0.07091956178761925, -0.010357294309542995, 0.021346612702439278, 0.016802930089689493, 0.016903522774512396, 0.032340673841323414, 0.007857870121654027, -0.017329509717866006, 0.04173632021466345, 0.012554695216177997, -0.06429847415266628, -0.00968944399985983, 0.012790894057246467, 0.052856231460059855, -0.02573782741879896, 0.019596945277049655, 0.0355210081409275, -0.011006288511253664, -0.009977055765767354, 0.019689063270884453, 0.012754475666217461, -0.01323126640251889, -0.00015030655247344135, -0.016018183903431828, -0.01952300017862112, -0.008958742421593033, -0.019383875085308543, -0.005635806867470939, -0.004597106224700637, 0.0025128668876894886, 0.025588850632059477, -0.0044013364721494095, 0.03343207161791574, 0.0499405350193516, -0.02022545856849576, -0.024693466248392366, 0.026018299430672433, 0.01717794457561357, 0.002348500440097088, 0.011404743475596625, -0.00921023880646681, -0.014131631618073234, -0.03549418824754468, 0.0014697685060345023, -0.003434394176875632, 0.0269289883170863, 0.023722909764586776, 0.009799338881367069, 0.05677702553961919, -0.031710540205265814, -0.07131976256822051, 0.03665449792904214, -0.002036793375967747, 0.05488680087524837, 0.023105622496254467, 0.09337779602577143, -0.00231971296154267, 0.010063535207180656, -0.01293823980746531, 0.017230608644703504, 0.02211285957107628, 0.008808177836584729, 0.031119567185125402, 0.03614945180504829, 0.015001717847314138, 0.01989154147222031, 0.0056178452480054085, -0.029791676072872784, 0.009448787153938343, 0.005767406636169137, -0.05863738402255414, 0.055811317646283956, -0.031044400937302895, 0.02500077471252984, -0.009042780476959264, 0.02811060120322967, 0.01596904593284234, -0.04293944415193668, 0.002836197418776539, -0.0020548601069883634, -0.02460027461070501, -0.0068094524150701, -0.00045493334739379193, 0.026010954312754735, -0.025722378177255027, -0.042864642425750846, 0.002530937754411716, -0.014079771445272431, -0.004911560169256569, 0.001648235862752711, 0.006831663151671753, -0.008880501901344748, 0.007357444469122321, 0.07841255135327242, 0.006534356159113161, 0.028034752960465564, 0.05217272101670474, -0.05510115555386593, 0.020581096535239162, -0.04747272254965116, -0.04245412248134138, -0.01986433228740111, -0.03230453770647274, 0.01962893934133994, 0.007015381388215813, -0.0004897232211922963, -0.020053935256816816, 0.028509085748873952, 0.06613713020773605, -0.0012495674924857259, -0.006755904219497998, -0.04540875857803117, -0.006255164333823541, 0.013990610563723152, -0.009265525420146524, -0.0576244201266161, 0.06454888062376665, 0.010071429630338195, 0.03941182176055469, -0.005701946139743362, 0.0164039954849659, -0.018309394798076667, -0.015329378117752184, 0.004481582830511625, -0.0001690483708885436, -0.013992690455661946, 0.004026913814879813, 0.0508103981787264, 0.06220570036358217, 0.06821925307183034, -0.02972636929712813, 0.018231307429267228, 0.008951625455569007, -0.011740316502239983, -0.006584744171986618, 0.005261954542281731, -0.031358160495692164, 0.02767942425257792, 0.05490896841757481, -0.012598706799115154, 0.04577388590003046, 0.01893709066216749, -0.013272610249020062, -0.03693189198538399, 0.052827669481864535, -0.03049476102239384, -0.026124013170066542, -0.0306425080943504, -0.021626888859015484, 0.01708927179215175, 0.024843603488345616, 0.04183953942174791, -0.018790369015512683, 0.04256110222927197, -0.01362417359746416, -0.06675999898056721, 0.003159391306509437, -0.07180096502910532, -0.03429202926505494, 0.010887869183378595, 0.029130631681559944, 0.02807114453105118, -0.007671204837380239, -0.021951983072519454, 0.022523210118289214, 0.04929679471539422, 0.04063870830049249, -0.045134661769959526, -0.017872007829769387, 0.005151397143689807, 0.06643596901741355, 0.007714516338663477, 0.002367221652810992, 0.017549822363216925, -0.0014344311111475997, -0.009989827102712064, -0.007652306983313501, 0.03276296384634109, 0.0048848457359636464, 0.009394608186412962, -0.020213722410803978, -0.04599183901001949, 0.03352203961206253, -0.042289964757670134, -0.02733410274528964, -0.03137365066599839, -0.014411792366674815, 0.010413616464115509, -0.010629713338168938, 0.07035218086336895, -0.0015968038028671893, 0.032848148710265035, -0.0084954907170146, -0.0330708111199992, -0.06224630020681689, -0.011150112350875499, 0.006136842734697253, -0.04494543464919096, 0.05617104869121611, -0.04195801558504906, 0.01854640012761996, 0.021188728772055583, -0.040707372302230246, 0.0036581853177999925, 0.0060156785053483985, 0.05773386357909731, 0.005060981187025026, -0.024652857916427913, 0.025022428183817252, -0.0827557355798232, -0.01795603706358487, -0.03238560835209652, 0.04641215439839316, 0.018119351336777622, -0.026488481679164427, 0.0016041278418103123, -0.01156208316621753, -0.026106280347108983, 0.07370615018395252, 0.034604356982606554, -0.051736851438125814, -0.008538823013307661, 0.011309581040408332, 0.056640517197135164, 0.03809484225470203, -0.002785677822111406, 0.014504016846877847, 0.050153814937364025, 0.03157086024024256, -0.03061695759190082, 0.03795247940289864, 0.003510619550545268, -0.0007831549868709473, -0.013062128851371521, -0.024021417550752724, 0.04866706390722239, 0.015725697584973376, -0.009595576902406979, -0.02794730852035047, 0.053271568865340534, -0.061542394483827055, -0.02592196546464463, -0.0621192752977582, -0.09651446803258067, -0.02469585115841286, -0.03094958084904083, 0.010862674921726405, -0.011441153519666018, -0.01750491692162886, 0.04993968234593367, -0.01898728525508128, 0.03120948868674752, -0.058011467192842565, -0.012739013603438468, -0.04933540173135603, -0.006672409721605748, 0.038437994569110866, 0.047016334850680484, -0.00803647121392723, -0.035576584397718894, 0.052096823348137145, 0.009878337296399737, 0.0064239674190188145, 0.0037310568722971537, 0.04610381551055631, 0.04331424822162683, -0.02662408394113811, 0.05073922763949365, 0.051566103909037105, 0.016015987735817708, 0.004852296024491176, 0.06056453015289304, -0.01873146433126438, -0.009019711112035133, -0.027067208099370153, -0.03914782128358444, 0.0059970406757684615, -0.0014704817757061307, 0.026062236494523425, 0.016911816894543293, -0.026013866111590945, -0.052882065954201134, 0.0019864590349063533, -0.030050610444361973, 0.0321807910102508, -0.044109996722129875, -0.007291476981409495, 0.050425732631383106, 0.04931942752621962, -0.01135305602413783, -0.022993107095841386, -0.004407793075950539, 0.022424917063607937, 0.0329958455379907, -0.07865659258567813, 0.0280798444909868, -0.04695397623162482, -0.023787078954573344, 0.004195475805261176, -0.006859669071422325, 0.016246903719593503, 0.012078208133641102, 0.0007732042267012298, 0.00536556380369155, -0.005175859386346448, 0.02823433659398111, 0.016909634929848618, 0.011420059656529776, 0.005465219731084866, -0.044073543306247805, 0.024901564871252935, -0.00783586832517539, 0.015513991146492824, 0.08761389959144254, 0.00028138689100188225, 0.04792286622619287, 0.03893456668029716, 0.010171615083485934, 0.008917543908271373, -0.04180401203109425, -0.004023703466385451, 0.020020534725593217, -0.005722672177397898, 0.009889954215505217, -0.003110271966792498, -0.022511104922804528, -0.1173011735375188, 0.03184304427250888, 0.002887899564912, 0.013675399192313194, -0.03278602942728742, -0.019532284570779636, -0.02246688329455633, -0.011622723975369283, -0.019080110834736094, 0.01721879089371022, -0.03733272395148291, -0.009910184958279809, -0.01763091231428701, 0.002749344693478309, 0.030490014306820766, 0.034938746140507855, 0.04624608567589026, 0.029231699837640006, 0.048962492329843454, 0.009627888904601306, 0.02690167345180243, -0.059077614820964124, -0.035674629981507694, 0.03867948670760955, -0.007137691763128465, -0.004375585395942654, 0.07479916886840011, 0.010115390354800892, -0.02457160440155629, 0.047049232190647, 0.030292676151464394, -0.017711135548590908, -0.005316888177266782, 0.03322862419875073, -0.04568279407158567, 0.05357001397414723, 0.022993103484735464, 0.001160387849005977, -0.01295160570189318, 0.030443797877143484, -0.047277050189414194, 0.02355950726327705, 0.027730443278708845, 0.043858108607521935, -0.028688304947057635, 0.035449985348394226, 0.025628097370710742, 0.025280364988772015, -0.029571790069432502, 0.023411132951084664, 0.023333624963097193, 0.05696862903985612, 0.005561445062402175, -0.031248988593948472, 0.05367288941822468, -0.06285953602603903, -0.0005597888053650648, 0.039490327781412746, 0.0232399347124437, -0.03025477057232343, 0.008536068626276939, 0.015718616289656404, -0.040939149485824396, 0.0033950160137695903, 0.045239612383741905, 0.005499864968872756, 0.007148549221157738, 0.040163743232817864, -0.035377654906124385, 0.004860808880546464, -0.03729688371983781, -0.028242007484452603, 0.014473144386231422, 0.020103928686625697, -0.03149993774734404, -0.00046404415135873353, 0.08987178490476, -0.005797951693868368, -0.006907992297814797, 0.049685624578974984, 0.05256857562682219, -0.02230340301295956, 0.01730942434876947, 0.00261268213383308, -0.013369154979770686, 0.05418813628014863, -0.027357375458538336, -0.026676018115363666, 0.017204038886792183, 0.058057872485282734, -0.03569172921992621, 0.009538559983844555, 0.014372053892039613, 0.007891497012824576, -0.027593571424743536, -0.033648286403450495, -0.07637311823913895, -0.02426039458926108, -0.05066105914961183, -0.004141268122904401, 0.010960056243137213, 0.015868281638289607, 0.04872352278360219, -0.03510274659237816, -0.0035320545107919363, -0.027487206850467325, -0.06501804993776698, 0.01244680732326278, -0.046441136475458694, 0.011199147770654175, 0.02987218865630449, 0.01766489107194864, -0.022371279215409705, 0.01150049328504933, -0.009493757906234553, 0.03372236235792888, 0.014110999234335052, -0.017446216041406417, 0.02511014699304057, -0.021838600341250652, 0.05238948929174141, 0.007762338366032931]] \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/data/rotation_matrix_alebo.npy b/mylib/lib_BAxUS/BAxUS/data/rotation_matrix_alebo.npy new file mode 100644 index 0000000..59d4218 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/data/rotation_matrix_alebo.npy differ diff --git a/mylib/lib_BAxUS/BAxUS/docs/LICENSE.md b/mylib/lib_BAxUS/BAxUS/docs/LICENSE.md new file mode 100644 index 0000000..0527150 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/LICENSE.md @@ -0,0 +1,43 @@ +## License + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by the text below. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under this License. + +This License governs use of the accompanying Work, and your use of the Work constitutes acceptance of this License. + +You may use this Work for any non-commercial purpose, subject to the restrictions in this License. Some purposes which can be non-commercial are teaching, academic research, and personal experimentation. You may also distribute this Work with books or other teaching materials, or publish the Work on websites, that are intended to teach the use of the Work. + +You may not use or distribute this Work, or any derivative works, outputs, or results from the Work, in any form for commercial purposes. Non-exhaustive examples of commercial purposes would be running business operations, licensing, leasing, or selling the Work, or distributing the Work for use with commercial products. + +You may modify this Work and distribute the modified Work for non-commercial purposes, however, you may not grant rights to the Work or derivative works that are broader than or in conflict with those provided by this License. For example, you may not distribute modifications of the Work under terms that would permit commercial use, or under terms that purport to require the Work or derivative works to be sublicensed to others. + +In return, we require that you agree: + +1. Not to remove any copyright or other notices from the Work. + +2. That if you distribute the Work in Source or Object form, you will include a verbatim copy of this License. + +3. That if you distribute derivative works of the Work in Source form, you do so only under a license that includes all of the provisions of this License and is not in conflict with this License, and if you distribute derivative works of the Work solely in Object form you do so only under a license that complies with this License. + +4. That if you have modified the Work or created derivative works from the Work, and distribute such modifications or derivative works, you will cause the modified files to carry prominent notices so that recipients know that they are not receiving the original Work. Such notices must state: (i) that you have changed the Work; and (ii) the date of any changes. + +5. If you publicly use the Work or any output or result of the Work, you will provide a notice with such use that provides any person who uses, views, accesses, interacts with, or is otherwise exposed to the Work (i) with information of the nature of the Work, (ii) with a link to the Work, and (iii) a notice that the Work is available under this License. + +6. THAT THE WORK COMES "AS IS", WITH NO WARRANTIES. THIS MEANS NO EXPRESS, IMPLIED OR STATUTORY WARRANTY, INCLUDING WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT. ALSO, YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +7. THAT NEITHER UBER TECHNOLOGIES, INC. NOR ANY OF ITS AFFILIATES, SUPPLIERS, SUCCESSORS, NOR ASSIGNS WILL BE LIABLE FOR ANY DAMAGES RELATED TO THE WORK OR THIS LICENSE, INCLUDING DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, TO THE MAXIMUM EXTENT THE LAW PERMITS, NO MATTER WHAT LEGAL THEORY IT IS BASED ON. ALSO, YOU MUST PASS THIS LIMITATION OF LIABILITY ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +8. That if you sue anyone over patents that you think may apply to the Work or anyone's use of the Work, your license to the Work ends automatically. + +9. That your rights under the License end automatically if you breach it in any way. + +10. Uber Technologies, Inc. reserves all rights not expressly granted to you in this License. diff --git a/mylib/lib_BAxUS/BAxUS/docs/Makefile b/mylib/lib_BAxUS/BAxUS/docs/Makefile new file mode 100644 index 0000000..73a28c7 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/mylib/lib_BAxUS/BAxUS/docs/_templates/layout.html b/mylib/lib_BAxUS/BAxUS/docs/_templates/layout.html new file mode 100644 index 0000000..1ce785c --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/_templates/layout.html @@ -0,0 +1,12 @@ +{% extends "!layout.html" %} + +{% block menu %} +{{ super() }} +

+ Indices +

+ +{% endblock %} \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/docs/cli_options.rst b/mylib/lib_BAxUS/BAxUS/docs/cli_options.rst new file mode 100644 index 0000000..3c46619 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/cli_options.rst @@ -0,0 +1,124 @@ + +Command line options +-------------------- + +.. list-table:: + :widths: 15 10 25 10 40 + :class: longtable + :header-rows: 1 + + * - **Name** + - **Shortcut** + - **Full argument** + - **Default** + - **Description** + * - Algorithm + - -a + - --algorithms + - baxus + - The algorithm to run. Has to be from baxus, embedded_turbo_target_dim, embedded_turbo_effective_dim, embedded_turbo_2_effective_dim, random_search + * - Function + - -f + - --functions + - None + - One ore several test functions. Has to be from hartmann6, branin2, rosenbrock5, rosenbrock10, ackley, rosenbrock, levy, dixonprice, griewank, michalewicz, rastrigin, svm, lasso-high, lasso-dna, lasso-high, lasso-medium, lasso-leukemia, lasso-rcv1, lasso-breastcancer, lasso-diabetes, lasso-simple, lasso-hard, mopta08, hartmann6in1000_rotated, shiftedackley10. + * - Input dimensionality + - -id + - --input-dim + - 100 + - Input dimensionality of the function. This is overriden when the function has a fixed dimensionality. + * - Target dimensionality + - -td + - --target-dim + - 10 + - (Initial) target dimensionality of the function. Whether initial or not depends on the algorithm. Initial for ``BAxUS`` as it adapts the target dimensionality. + * - Acquisition function + - None + - --acquisition-function + - ts + - Either ``ts`` (Thompson sampling) or ``ei`` (Expected improvement) + * - Embedding type + - None + - --embedding-type + - baxus + - Either ``baxus`` (for the BAxUS embedding) or ``hesbo`` (for the HeSBO embedding) + * - Adjust initial target dimensionality + - None + - --adjust-initial-target-dimension + - not set + - Whether to adjust the initial target dimensionality as described in the BAxUS paper. + * - Number of initial samples + - -n + - --n-init + - None (set to target dimensionality + 1 if not set) + - Number of DOE samples. + * - Number of repetitions + - -r + - --num-repetitions + - 1 + - Number of repetitions of the run. + * - Number of evaluations + - -m + - --max-evals + - 300 + - Number of evaluations. Cma-ES might use a few more. + * - Initial baselength + - -l + - --initial-baselength + - 0.8 + - The initial base length of the trust region (default value is as in the TuRBO paper). + * - Minimum baselength + - -lmin + - --min-baselength + - 0.5^7 + - The minimum base length a trust region is allowed to obtain (default value is as in the TuRBO paper). + * - Maximum baselength + - -l_max + - --max-baselength + - 1.6 + - The maximum base length a trust region is allowed to obtain (default value is as in the TuRBO paper). + * - Noise standard deviation + - None + - --noise-std + - 0 + - The standard deviation of the noise. Whether this is used or not depends on the benchmark. It is generally only recognized for synthetic benchmarks like ``Branin2`` but also for the synthetic ``Lasso`` versions. + * - Results directory + - None + - --results-dir + - results + - The directory to which the results are written. Relative to the path from which the run was started. + * - Run description + - None + - --run-description + - None + - Short description that will be added to the run directory + * - MLE multistart samples + - None + - --multistart-samples + - 100 + - Number of multistart samples for the MLE GD optimization. Samples will be drawn from latin hypercube + * - Multistarts after sampling + - None + - --multistart-after-sample + - 10 + - Only recognized for '--mle-optimization sample-and-choose-best'. Number of multi-start gradient descent optimization out of the ``--multistart-samples`` best ones. + * - MLE optimization method + - None + - --mle-optimization + - sample-and-choose-best + - Either ``multistart-gd`` or ``sample-and-choose-best``. + * - Number of MLE gradient updates + - None + - --mle-training-steps + - 50 + - Number of GD steps in MLE maximization. + * - Budget until input dimensionality + - None + - --budget-until-input-dim + - 0 + - The budget after which BAxUS will roughly reach the input dimensionality (see paper for details). If ``0``\ : this setting is ignored + * - Verbose mode + - -v + - --verbose + - not set + - Whether to print verbose messages \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/docs/conf.py b/mylib/lib_BAxUS/BAxUS/docs/conf.py new file mode 100644 index 0000000..740e220 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/conf.py @@ -0,0 +1,58 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath('../')) + +# -- Project information ----------------------------------------------------- + +project = 'BAxUS' +copyright = '2022, anonymous' +author = 'anonymous' + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.napoleon', "m2r2", + "sphinx_rtd_theme"] +pdf_stylesheets = ['twocolumn'] +source_suffix = ['.rst', '.md'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +latex_elements = { + 'extraclassoptions': 'openany,oneside', + 'preamble': r'''\usepackage{makeidx} \usepackage[columns=1]{idxlayout} \makeindex +''' +} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/mylib/lib_BAxUS/BAxUS/docs/custom_functions.rst b/mylib/lib_BAxUS/BAxUS/docs/custom_functions.rst new file mode 100644 index 0000000..c071f2a --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/custom_functions.rst @@ -0,0 +1,75 @@ + +Optimizing custom functions +--------------------------- + +Custom benchmark class +^^^^^^^^^^^^^^^^^^^^^^ + +For practical use cases, you want to optimize your own functions instead of running benchmark functions. Let's see how +we implement benchmark functions. As an example, :class:`baxus.benchmarks.real_world_benchmarks.MoptaSoftConstraints` implements +:class:`baxus.benchmarks.benchmark_function.SyntheticBenchmark`, which means in particular that it has its +own ``__call__`` function. + +Let's look at the ``__call__`` function +of :class:`baxus.benchmarks.real_world_benchmarks.MoptaSoftConstraints` : + +.. code-block:: python + + def __call__(self, x): + super(MoptaSoftConstraints, self).__call__(x) + x = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + + vals = np.array([self._call(y) for y in x]).squeeze() + return vals + +which consists of some checks that ensure that we use the internal ``self._call`` function correctly. + +If you want to use BAxUS with a custom function, you can just use this implementation and replace +``self._call`` in the line +``vals = np.array([self._call(y) for y in x]).squeeze()`` +with a call to your own function expecting a 1D numpy array. + +How do I register my new function? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For this we need to look at the :class:`baxus.util.parsing.parse` function. +The first thing to do is to append your benchmark to the list of existing benchmarks, +currently consisting of + +.. code-block:: python + + required_named.add_argument( + "-f", + "--functions", + nargs="+", + choices=[ + "hartmann6", + "branin2", + ..., + "MY_NEW_NAME" # <---------------- ADD THIS LINE + ], + required=True, + ) + +Next, we have to register the new name in the :class:`baxus.util.parsing.fun_mapper>` function: + +.. code-block:: python + + def fun_mapper(): + return { + **{ + "hartmann6": Hartmann6, + "branin2": Branin2, + "rosenbrock2": functools.partial(RosenbrockEffectiveDim, effective_dim=2), + ..., + "MY_NEW_NAME": MyBenchmarkImplementation # <--------- ADD THIS LINE + }, + **_fun_mapper, + } + +and that's it. diff --git a/mylib/lib_BAxUS/BAxUS/docs/getting_started.md b/mylib/lib_BAxUS/BAxUS/docs/getting_started.md new file mode 100644 index 0000000..90e5593 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/getting_started.md @@ -0,0 +1,14 @@ +## Getting started + +The main file is `benchmark_runner.py` in the project root. +It can be configured with command line arguments (see [Command Line Options](cli_options.html)) + +For example, to run `BAxUS` for 1,000 function evaluations on a Branin2 function with input dimensionality 100 for one +repetition run + +``` +python3 benchmark_runner.py -id 100 -td 1 -n 10 -r 1 -m 1000 -f branin2 -a baxus --adjust-initial-target-dimension +``` + +Note that we need to pass an initial target dimensionality with `-td 1` even though this is adjusted later by passing +the option `--adjust-initial-target-dimension`- \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/docs/index.rst b/mylib/lib_BAxUS/BAxUS/docs/index.rst new file mode 100644 index 0000000..8e91fc7 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/index.rst @@ -0,0 +1,24 @@ +Welcome to BAxUS's documentation! +================================= + +.. mdinclude:: notices.md + +.. toctree:: + installation.md + getting_started.md + reproducing.md + custom_functions + cli_options + troubleshooting + baxus + LICENSE.md + :maxdepth: 2 + :caption: Contents: + +* :ref:`genindex` + +Indices and tables +================== +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/docs/installation.md b/mylib/lib_BAxUS/BAxUS/docs/installation.md new file mode 100644 index 0000000..fa8be8c --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/installation.md @@ -0,0 +1,93 @@ +# Installation +We explain how to install BAxUS and how to build the docs. + +## Installation + +You have 3 options for installing `BAxUS`. +Please make sure to install the following packages before running the `BAxUS` installation. +We assume that you have a Debian Buster based Linux distribution. Please use a Docker image +if you are working with a different distribution: +``` +apt-get update && apt-get -y upgrade && apt-get -y install libsuitesparse-dev libatlas-base-dev swig libopenblas-dev libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-2.0-0 libsdl2-ttf-2.0-0 libsdl2-dev +``` + +### `pip` installation + +`` +python3 -m pip install baxus +`` + +### Installation from source + +First install required software: + +```bash +apt-get update && apt-get -y upgrade && apt-get -y install libsuitesparse-dev libatlas-base-dev swig libopenblas-dev libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-2.0-0 libsdl2-ttf-2.0-0 libsdl2-dev +``` + +Then install with the `setup.py`: + +```bash +cd baxus +pip install . +``` + +or with the requirements.txt: + +```bash +cd baxus +pip install -r requirements.txt +``` + +### Docker image + +Alternatively, use the Docker installation. +We do not share the Docker image to ensure anonymity. +However, you can build the Docker image yourself with the provided `Dockerfile`: + +First, [install Docker](https://docs.docker.com/engine/install/). +Next, build the Docker image + +```bash +cd baxus +sudo docker build -t baxus +``` + +By default, BAxUS stores all results in a directory called `results`. +To get the results on the host machine, first create this directory and mount it into the Docker container: + +```bash +mkdir results +sudo docker run -v "$(pwd)/results":/app/results baxus /bin/bash -c "python benchmark_runner.py -id 100 -td 1 -f branin2 --adjust-initial-target-dimension" +``` + +After the run completed, the results can be obtained in the `./results` directory. + +## Building the docs + +To build the docs, you need to install additional packages: +```bash +pip install sphinx m2r2 sphinx_rtd_theme +``` +If you want to build the PDF documentation, you further need to install +```bash +sudo apt-get install texlive texlive-latex-extra latexmk +``` + +The docs are located in the docs directory. +To build the API doc, run +```bash +cd docs +sphinx-apidoc -o . ../baxus +``` + +To build the HTML version, run +```bash +make html +``` +and for the PDF version, +```bash +make latexpdf +``` + +The docs are located in `docs/_build/html` or `docs/_build/pdf`. \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/docs/make.bat b/mylib/lib_BAxUS/BAxUS/docs/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/mylib/lib_BAxUS/BAxUS/docs/notices.md b/mylib/lib_BAxUS/BAxUS/docs/notices.md new file mode 100644 index 0000000..36a34df --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/notices.md @@ -0,0 +1,4 @@ +* `BAxUS` is tested under Python 3.8, 3.9, and 3.10. +* We support only Linux which is due to a dependency to the Lasso benchmarks. +* We tested under Debian Buster and Debian Bullseye (Ubuntu distributions based on these Debian versions work as well, + e.g., Ubuntu 18.04 and Ubuntu 20.04) \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/docs/reproducing.md b/mylib/lib_BAxUS/BAxUS/docs/reproducing.md new file mode 100644 index 0000000..f337fa2 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/reproducing.md @@ -0,0 +1,191 @@ +# Reproducing the results from the paper + +We explain here how to reproduce the `BAxUS` and the `EmbeddedTuRBO` results from our paper. +Please increase the number of repetitions `-r` if too low. + +## Main paper results + +### Figure 2 + +For Figure 2, we used the following code: + +```python +import math +import numpy as np +from matplotlib import pyplot as plt +``` + +Define the HeSBO success probability: +```python +def succ_hesbo(D,d,de): + """ + HeSBO success probability (independent of D) + """ + if d 0], axis=0) + print(mean.shape) + sortkeys = np.array([i+1 for i in range(len(mean)) if mean[i]>0]) + hist_bins_format = [f"{f'{hb:.2f}'.replace('0.','.')}" for hb in mean] + print("sc",sortkeys) + + color = "#5D3A9B" if bs == EmbeddingType.HESBO else "#E66100" + label = "HeSBO" if bs == EmbeddingType.HESBO else "BAxUS" + ax.grid(which='major', color='#CCCCCC', linestyle='--') + ax.grid(which='minor', color='#CCCCCC', linestyle=':') + bar = ax.bar(sortkeys+group, mean[mean > 0], yerr=stderr, width=bar_width, label=label, color=color) + ax.bar_label(bar, fmt='%.2f') +ax.set_xlabel("\# target dims. containing an important input dim.", size=14) +ax.set_ylabel("empirical probability", size=14) +ax.legend(loc="upper left") +fig.tight_layout() +``` + +### Figure 7 + +To reproduce the results from Figure 7, run +```bash +python benchmark_runner.py -a baxus -f lasso-hard -m 1000 -r 20 -id 500 -td 1 --adjust-initial-target-dimension --n-init 10 +``` +for the BAxUS result and +```bash +python benchmark_runner.py -a embedded_turbo_target_dim -f lasso-hard -m 1000 -r 20 -id 500 -td TARGET_DIMENSION --n-init 10 +``` +where you replace `TARGET_DIMENSION` with 2, 10, 20, 30, 40, 50, 60, 70, 80, 90, and 100. + +### Figure 8 +To reproduce the results from Figure 8, run +```bash +python benchmark_runner.py -a ALGORITHM -f lasso-dna -m 1000 -r 20 -id 500 -td 1 --adjust-initial-target-dimension --n-init 10 +``` +where you replace `ALGORITHM` with `baxus` or `random_search`. \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/docs/troubleshooting.rst b/mylib/lib_BAxUS/BAxUS/docs/troubleshooting.rst new file mode 100644 index 0000000..6205355 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/docs/troubleshooting.rst @@ -0,0 +1,28 @@ + +Troubleshooting +--------------- + +Mopta08 Executables +^^^^^^^^^^^^^^^^^^^ + +The executables for the :class:`baxus.benchmarks.real_world_benchmarks.MoptaSoftConstraints` +benchmark are not contained in the repository but downloaded when necessary. +We support four different architectures/operating systems: ARM (32 bit), Windows (64 bit), Linux (64 bit), Linux (32 bit). + +The files are automatically downloaded and made executable. +However, this might cause problems if there are no sufficient permissions for writing or making the file executable. +In that case, please download the correct file and move it to ``baxus/benchmarks/mopta08/``. +The files can be downloaded at + +* 64bit Windows: ``_ +* 32bit ARM: ``_ +* 32bit Linux: ``_ +* 64bit Linux: ``_ + +Slice Locatization Data +^^^^^^^^^^^^^^^^^^^ + +Similarly, it can happen that there are no sufficient permissions to download the slice localization data +for the :class:`baxus.benchmarks.real_world_benchmarks.SVMBenchmark` benchmark. +Please download it from ``_ +and move it to ``baxus/data/``. \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/requirements.txt b/mylib/lib_BAxUS/BAxUS/requirements.txt new file mode 100644 index 0000000..ea9af99 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/requirements.txt @@ -0,0 +1,8 @@ +numpy>=1.21 +pandas>=1.4 +torch>=1.3 +lasso-bench-fork-leoiv==0.0.6 +botorch>=0.6 +gpytorch>=1.6,<=1.8.1 +scikit-learn>=1.1 +parameterized>=0.8 \ No newline at end of file diff --git a/mylib/lib_BAxUS/BAxUS/setup.py b/mylib/lib_BAxUS/BAxUS/setup.py new file mode 100644 index 0000000..80666c7 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/setup.py @@ -0,0 +1,29 @@ +from pathlib import Path + +from setuptools import setup, find_packages + +this_directory = Path(__file__).parent +long_description = (this_directory / "README.md").read_text() +setup( + name="BAxUS", + version="0.0.8", + author="Leonard Papenmeier", + author_email="leonard.papenmeier@cs.lth.se", + packages=find_packages(), + install_requires=[ + "numpy>=1.21", + "pandas>=1.4", + "torch>=1.3", + "lasso-bench-fork-leoiv==0.0.6", + "botorch>=0.6", + "gpytorch<=1.8.1", + "scikit-learn>=1.1", + "parameterized>=0.8", + ], + exclude_package_data={'': ["results/*", "tests/*"]}, + long_description=long_description, + long_description_content_type='text/markdown', + entry_points={ + 'console_scripts': ['benchmark-runner=baxus.util.console_entry_point:bench'], + } +) diff --git a/mylib/lib_BAxUS/BAxUS/tests/__init__.py b/mylib/lib_BAxUS/BAxUS/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mylib/lib_BAxUS/BAxUS/tests/data/slice_localization_data.csv.xz b/mylib/lib_BAxUS/BAxUS/tests/data/slice_localization_data.csv.xz new file mode 100644 index 0000000..47dfe21 Binary files /dev/null and b/mylib/lib_BAxUS/BAxUS/tests/data/slice_localization_data.csv.xz differ diff --git a/mylib/lib_BAxUS/BAxUS/tests/test_baxus.py b/mylib/lib_BAxUS/BAxUS/tests/test_baxus.py new file mode 100644 index 0000000..c632541 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/tests/test_baxus.py @@ -0,0 +1,19 @@ +from unittest import TestCase + +import numpy as np + +from baxus.util.data_utils import join_data + + +class BAxUSTestSuite(TestCase): + + def test_join_data(self): + x = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) + x1 = join_data(X=x, dims_and_bins={2: 2}) + self.assertTrue(np.allclose(x1[:, 2], x1[:, 3])) + self.assertTrue(np.allclose(x[:, 2], x1[:, 3])) + self.assertTrue(np.allclose(x[:, 0], x1[:, 0])) + self.assertTrue(np.allclose(x[:, 1], x1[:, 1])) + self.assertTrue(np.allclose(x[:, 2], x1[:, 2])) + self.assertTrue(x1.shape == (2, 4)) + self.assertTrue(x.shape == (2, 3)) diff --git a/mylib/lib_BAxUS/BAxUS/tests/test_benchmark_function.py b/mylib/lib_BAxUS/BAxUS/tests/test_benchmark_function.py new file mode 100644 index 0000000..2b4d848 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/tests/test_benchmark_function.py @@ -0,0 +1,183 @@ +import os +import shutil +from typing import List, Union +from unittest import TestCase, mock + +import numpy as np +import pandas as pd +import pytest + +from baxus.benchmarks import Benchmark, SyntheticBenchmark, SVMBenchmark, MoptaSoftConstraints +from baxus.util.exceptions import OutOfBoundsException, BoundsMismatchException + + +class BenchmarkTestSuite(TestCase): + def test_init(self): + benchmark = Benchmark(100, np.ones(100), np.zeros(100), noise_std=0) + self.assertEqual(100, benchmark.dim) + self.assertTrue(np.allclose(np.ones(100), benchmark.ub_vec)) + self.assertTrue(np.allclose(np.zeros(100), benchmark.lb_vec)) + self.assertEqual("Benchmark", benchmark.fun_name) + + def test_fun(self): + with pytest.raises(NotImplementedError): + benchmark = Benchmark(100, np.ones(100), np.zeros(100), noise_std=0) + benchmark(3) + + def test_lower_bound_larger_than_upper_bound(self): + with pytest.raises(OutOfBoundsException): + benchmark = Benchmark(100, np.zeros(100), np.ones(100), noise_std=0) + + def test_one_bound_dim_unequal_dim(self): + with pytest.raises(BoundsMismatchException): + benchmark = Benchmark(100, np.ones(99), np.zeros(100), noise_std=0) + + def test_both_bounds_dim_unequal_dim(self): + with pytest.raises(BoundsMismatchException): + benchmark = Benchmark(100, np.ones(99), np.zeros(99), noise_std=0) + + +class SyntheticBenchmarkTestSuite(TestCase): + def setUp(self) -> None: + class SyntheticBenchmarkInstance(SyntheticBenchmark): + """ + Simple class to allow for instantiation + """ + + def __init__(self, dim: int, ub: np.ndarray, lb: np.ndarray): + super().__init__(dim, ub, lb, noise_std=0) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]): + return super().__call__(x) + + self.synthetic_benchmark = SyntheticBenchmarkInstance + + return super().setUp() + + def test_init(self): + benchmark = self.synthetic_benchmark(100, np.ones(100), np.zeros(100)) + self.assertEqual(100, benchmark.dim) + self.assertTrue(np.allclose(np.ones(100), benchmark.ub_vec)) + self.assertTrue(np.allclose(np.zeros(100), benchmark.lb_vec)) + self.assertEqual("SyntheticBenchmarkInstance", benchmark.fun_name) + + def test_lower_bound_larger_than_upper_bound(self): + with pytest.raises(OutOfBoundsException): + benchmark = self.synthetic_benchmark(100, np.zeros(100), np.ones(100)) + + def test_one_bound_dim_unequal_dim(self): + with pytest.raises(BoundsMismatchException): + benchmark = self.synthetic_benchmark(100, np.ones(99), np.zeros(100)) + + def test_both_bounds_dim_unequal_dim(self): + with pytest.raises(BoundsMismatchException): + benchmark = self.synthetic_benchmark(100, np.ones(99), np.zeros(99)) + + def test_optimal_value(self): + benchmark = self.synthetic_benchmark(100, np.ones(100), np.zeros(100)) + self.assertIsNone(benchmark.optimal_value) + + +class SVMBenchmarkTestSuite(TestCase): + def setUp(self) -> None: + def _load_data(*args, **kwargs): + data_folder = "tests/data" + if data_folder is None: + data_folder = os.path.join(os.getcwd(), "data") + data = pd.read_csv( + os.path.join(data_folder, "slice_localization_data.csv.xz") + ).to_numpy() + X = data[:100, :385] + y = data[:100, -1] + return X, y + + load_data_mock = mock.MagicMock(side_effect=_load_data) + with mock.patch( + "baxus.benchmarks.real_world_benchmarks.SVMBenchmark._load_data", + load_data_mock, + ): + self.benchmark = SVMBenchmark() + + def test_init(self): + self.assertEqual(self.benchmark.dim, 388) + + def test_call_with_one_value_numpy_array(self): + input = np.random.uniform(low=self.benchmark.lb_vec, high=self.benchmark.ub_vec) + results = self.benchmark(input) + self.assertEqual(results.size, 1) + + def test_call_with_multiple_values_numpy_array(self): + input = np.random.uniform( + low=self.benchmark.lb_vec, + high=self.benchmark.ub_vec, + size=(5, self.benchmark.dim), + ) + results = self.benchmark(input) + self.assertEqual(len(results), 5) + + def test_call_with_one_value_list(self): + input = np.random.uniform( + low=self.benchmark.lb_vec, high=self.benchmark.ub_vec + ).tolist() + results = self.benchmark(input) + self.assertEqual(results.size, 1) + + def test_call_with_multiple_values_numpy_array(self): + input = np.random.uniform( + low=self.benchmark.lb_vec, + high=self.benchmark.ub_vec, + size=(5, self.benchmark.dim), + ).tolist() + results = self.benchmark(input) + self.assertEqual(len(results), 5) + + +class MoptaBenchmarkTestSuite(TestCase): + def test_init(self): + benchmark = MoptaSoftConstraints() + self.assertEqual(benchmark.dim, 124) + + def test_call_with_one_value_numpy_array(self): + benchmark = MoptaSoftConstraints() + input = np.random.uniform(low=benchmark.lb_vec, high=benchmark.ub_vec) + results = benchmark(input) + self.assertEqual(results.size, 1) + + def test_call_with_multiple_values_numpy_array(self): + benchmark = MoptaSoftConstraints() + input = np.random.uniform( + low=benchmark.lb_vec, high=benchmark.ub_vec, size=(5, benchmark.dim) + ) + results = benchmark(input) + self.assertEqual(len(results), 5) + + def test_call_with_one_value_list(self): + benchmark = MoptaSoftConstraints() + input = np.random.uniform(low=benchmark.lb_vec, high=benchmark.ub_vec).tolist() + results = benchmark(input) + self.assertEqual(results.size, 1) + + def test_call_with_multiple_values_numpy_array(self): + benchmark = MoptaSoftConstraints() + input = np.random.uniform( + low=benchmark.lb_vec, high=benchmark.ub_vec, size=(5, benchmark.dim) + ).tolist() + results = benchmark(input) + self.assertEqual(len(results), 5) + + def test_write_to_generated_dir(self): + benchmark = MoptaSoftConstraints() + tmp_dir = benchmark.directory_name + input = np.random.uniform(low=benchmark.lb_vec, high=benchmark.ub_vec) + benchmark(input) + self.assertTrue(os.path.exists(os.path.join(tmp_dir, "input.txt"))) + self.assertTrue(os.path.exists(os.path.join(tmp_dir, "output.txt"))) + + def test_write_to_given_dir(self): + tmp_dir = "".join([str(x) for x in np.random.randint(0, 10, 10)]) + benchmark = MoptaSoftConstraints(tmp_dir) + input = np.random.uniform(low=benchmark.lb_vec, high=benchmark.ub_vec) + benchmark(input) + self.assertTrue(os.path.exists(os.path.join(tmp_dir, "input.txt"))) + self.assertTrue(os.path.exists(os.path.join(tmp_dir, "output.txt"))) + shutil.rmtree(tmp_dir) diff --git a/mylib/lib_BAxUS/BAxUS/tests/test_benchmark_runner.py b/mylib/lib_BAxUS/BAxUS/tests/test_benchmark_runner.py new file mode 100644 index 0000000..b2dccbd --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/tests/test_benchmark_runner.py @@ -0,0 +1,269 @@ +import os +import tempfile +from unittest import TestCase, mock + +import pandas as pd +import pytest +from parameterized import parameterized + +from baxus.benchmark_runner import main +from baxus.util.exceptions import ArgumentError, EffectiveDimTooLargeException +from baxus.util.parsing import parse + + +def custom_name_func(testcase_func, param_num, param): + return "_".join(param.args[1].split(" ")) + + +def _load_data(*args, **kwargs): + data_folder = "tests/data" + if data_folder is None: + data_folder = os.path.join(os.getcwd(), "data") + data = pd.read_csv( + os.path.join(data_folder, "slice_localization_data.csv.xz") + ).to_numpy() + X = data[:100, :385] + y = data[:100, -1] + return X, y + + +@mock.patch('baxus.embeddedturbo.lzma') +class BenchmarkRunnerTestSuite(TestCase): + + def _parse_func(self, args): + """ + Use a temporary directory for runs instead of saving them to "results" + :param args: + :return: + """ + directory_file_descriptor = tempfile.TemporaryDirectory() + self.fd = directory_file_descriptor + directory_name = directory_file_descriptor.name + args = parse(args) + args.results_dir = directory_name + return args + + def tearDown(self) -> None: + self.fd.cleanup() + self.fd = None + + @parameterized.expand( + [ + # 1: test ts + ( + "-id 6 -td 1 -n 2 -r 1 -m 10 --noise-std 0.1 -f branin2 -a baxus -l 0.02 --acquisition-function ts", + "test ei acquisition function", + ), + # 2: test ei + ( + "-id 6 -td 1 -n 2 -r 1 -m 10 --noise-std 0.1 -f branin2 -a baxus -l 0.02 --acquisition-function ei", + "test ei acquisition function", + ), + # 5: test multiple repetitions + ( + "-id 6 -td 2 -n 2 -r 2 -m 10 --noise-std 0.1 -f rosenbrock5 -a baxus", + "test multiple repetitions", + ), + # 6: test target dim 1 + ( + "-id 6 -td 1 -n 2 -r 1 -m 10 --noise-std 0.1 -f rosenbrock5 -a baxus", + "test target dims", + ), + # 6: test target dim 2 + ( + "-id 6 -td 2 -n 2 -r 1 -m 10 --noise-std 0.1 -f rosenbrock5 -a baxus", + "test target dims", + ), + # 7: test input dim 6 + ( + "-id 6 -td 2 -n 2 -r 1 -m 5 --noise-std 0.1 -f rosenbrock5 -a baxus", + "test input dims", + ), + # 7: test input dim 7 + ( + "-id 7 -td 2 -n 2 -r 1 -m 5 --noise-std 0.1 -f rosenbrock5 -a baxus", + "test input dims", + ), + # 13: test mle optimization multistart-gd + ( + "-id 6 -td 2 -n 2 -r 1 -m 5 --noise-std 0.1 -f rosenbrock5 -a baxus " + "--mle-optimization multistart-gd", + "test mle optimization", + ), + # 13: test mle optimization sample-and-choose-best + ( + "-id 6 -td 2 -n 2 -r 1 -m 5 --noise-std 0.1 -f rosenbrock5 -a baxus " + "--mle-optimization sample-and-choose-best", + "test mle optimization", + ), + # 14: test multistart after sample 2 + ( + "-id 6 -td 2 -n 2 -r 1 -m 5 --noise-std 0.1 -f rosenbrock5 -a baxus " + "--multistart-after-sample 2", + "test multistart after sample", + ), + # 14: test multistart after sample 3 + ( + "-id 6 -td 2 -n 2 -r 1 -m 5 --noise-std 0.1 -f rosenbrock5 -a baxus " + "--multistart-after-sample 3", + "test multistart after sample", + ), + # 15: check if baxus is running + ( + "-id 7 -td 2 -n 2 -r 1 -m 8 -f " + "hartmann6 -a baxus", + "test if baxus is running", + ), + # 15: check if embedded_turbo_target_dim is running + ( + "-id 7 -td 2 -n 2 -r 1 -m 8 -f " + "hartmann6 -a embedded_turbo_target_dim", + "test if embedded_turbo_target_dim is running", + ), + # 15: check if embedded_turbo_effective_dim is running + ( + "-id 7 -td 2 -n 2 -r 1 -m 8 -f " + "hartmann6 -a embedded_turbo_effective_dim", + "test if embedded_turbo_effective_dim is running", + ), + # 15: check if embedded_turbo_2_effective_dim is running + ( + "-id 7 -td 2 -n 2 -r 1 -m 8 -f " + "hartmann6 -a embedded_turbo_2_effective_dim", + "test if embedded_turbo_2_effective_dim is running", + ), + # 15: check if random_search is running + ( + "-id 7 -td 2 -n 2 -r 1 -m 8 -f " + "hartmann6 -a random_search", + "test if random_search is running", + ), + # 20: test multistart samples 11 + ( + "-id 6 -td 2 -n 2 -r 1 -m 10 -f rosenbrock5 -a baxus --multistart-samples 11", + "test multistart samples", + ), + # 20: test multistart samples 12 + ( + "-id 6 -td 2 -n 2 -r 1 -m 10 -f rosenbrock5 -a baxus --multistart-samples 12", + "test multistart samples", + ), + # 21: test mle training steps 31 + ( + "-id 6 -td 2 -n 2 -r 1 -m 5 -f rosenbrock5 -a baxus --mle-training-steps 31", + "test mle training steps", + ), + # 21: test mle training steps 32 + ( + "-id 6 -td 2 -n 2 -r 1 -m 5 -f rosenbrock5 -a baxus --mle-training-steps 32", + "test mle training steps", + ), + ], + name_func=custom_name_func, + ) + def test_sequence(self, _, conf: str, __): + load_data_mock = mock.MagicMock(side_effect=_load_data) + with mock.patch( + "baxus.benchmarks.real_world_benchmarks.SVMBenchmark._load_data", + load_data_mock, + ): + with mock.patch("baxus.benchmark_runner.parse", + mock.MagicMock(side_effect=self._parse_func)): + main(conf.split(" ")) + + @parameterized.expand( + [ + ( + "-id 6 -td 2 -n 2 -r 1 -m 4 --noise-std 0.1 -lmin 0.8 -lmax 0.4 -f rosenbrock5 " + "-a baxus", + "test min length larger than max length", + ArgumentError + ), + ( + "-id 6 -td 10 -n 2 -r 1 -m 4 --noise-std 0.1 -f rosenbrock5 " + "-a baxus", + "test input dim smaller than target dim", + ArgumentError + ), + ( + "-id 4 -td 2 -n 2 -r 1 -m 4 --noise-std 0.1 -f rosenbrock5 " + "-a baxus", + "test input dim too small for rosenbrock5", + EffectiveDimTooLargeException + ), + ( + "-id 4 -td 2 -n 2 -r 1 -m 4 --noise-std 0.1 -f hartmann6 " + "-a baxus", + "test input dim too small for hartmann6", + EffectiveDimTooLargeException + ), + ( + "-id 4 -td 2 -n 2 -r 1 -m 4 --noise-std 0.1 -f rosenbrock10 " + "-a baxus", + "test input dim too small for rosenbrock10", + EffectiveDimTooLargeException + ), + ( + "-id 6 -td 2 -n 2 -r 1 --noise-std -0.3 -m 4 -f hartmann6 " + "-a baxus", + "test negative noise std", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 -bins 1 -a baxus", + "test one bin", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 -bins 0 -a baxus", + "test zero bins", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 -bins -1 -a baxus", + "test negative bins", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 --multistart-samples 0 -a baxus", + "test zero multistart samples", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 --multistart-samples -1 -a baxus", + "test negative multistart samples", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 --multistart-samples 5 --multistart-after-sample 6 -a baxus", + "test multistart after samples greater than initial multistart", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 --multistart-after-sample 0 -a baxus", + "test zero multistart after samples", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 --multistart-after-sample -1 -a baxus", + "test negative multistart after samples", + ArgumentError + ), + ( + "-id 6 -td 2 -n 2 -m 4 -f hartmann6 --mle-training-steps -1 -a baxus", + "test negative mle training steps", + ArgumentError + ), + ], + name_func=custom_name_func, + ) + def test_illegal_configurations(self, __, conf: str, _, exception): + load_data_mock = mock.MagicMock(side_effect=_load_data) + with mock.patch( + "baxus.benchmarks.real_world_benchmarks.SVMBenchmark._load_data", + load_data_mock, + ): + with mock.patch("baxus.benchmark_runner.parse", + mock.MagicMock(side_effect=self._parse_func)): + with pytest.raises(exception): + main(conf.split(" ")) diff --git a/mylib/lib_BAxUS/BAxUS/tests/test_projections.py b/mylib/lib_BAxUS/BAxUS/tests/test_projections.py new file mode 100644 index 0000000..c848253 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/tests/test_projections.py @@ -0,0 +1,114 @@ +from copy import deepcopy +from unittest import TestCase + +import numpy as np +import pytest + +from baxus.util.exceptions import OutOfBoundsException +from baxus.util.projections import AxUS + + +class ProjectionsTestSuite(TestCase): + def test_init(self): + axus = AxUS(input_dim=57, target_dim=23) + assert axus.target_dim == 23 + assert axus.input_dim == 57 + assert axus.S_prime.shape == (57, 23) + assert axus.S.shape == (23, 57) + for row in axus.S_prime: + self.assertEqual(1, np.sum(np.abs(row))) + + for col in axus.S.T: + self.assertEqual(1, np.sum(np.abs(col))) + + self.assertEqual(57, np.count_nonzero(axus.S)) + self.assertEqual(57, np.count_nonzero(axus.S_prime)) + + def test_project_down(self): + axus = AxUS(input_dim=6, target_dim=2) + axus.S = np.array( + [[-1, -1, 0, 1, 0, 0], [0, 0, 1, 0, -1, 1]], dtype=np.float32 + ) + Y = axus.project_down( + np.array([[0.3, 0.7, -0.4, 0.6, -0.1, -0.9]]).T + ).squeeze() + assert np.allclose(Y, np.array([-0.4, -1.2])) + + def test_project_up(self): + axus = AxUS(input_dim=6, target_dim=2) + axus.S = np.array( + [[-1, -1, 0, 1, 0, 0], [0, 0, 1, 0, -1, 1]], dtype=np.float32 + ) + Y = axus.project_up(np.array([[-0.4, 0.7]]).T).squeeze() + assert np.allclose(Y, np.array([0.4, 0.4, 0.7, -0.4, -0.7, 0.7])) + + def test_exceed_bounds_down(self): + with pytest.raises(OutOfBoundsException): + axus = AxUS(input_dim=6, target_dim=2) + axus.project_down(np.array([[0.3, 1.7, -0.4, 0.6, -0.1, -0.9]]).T) + + def test_exceed_bounds_up(self): + with pytest.raises(OutOfBoundsException): + axus = AxUS(input_dim=6, target_dim=2) + axus.project_up(np.array([[-1.4, 0.7]]).T) + + def test_compute_input_to_target_dim(self): + axus = AxUS(input_dim=40, target_dim=10) + target_to_input_dim = axus.target_to_input_dim + input_to_target_dim = axus.input_to_target_dim + + for k, v in input_to_target_dim.items(): + # k: input dim + # v : target dim + self.assertIn(k, target_to_input_dim[v]) + + def test_reset(self): + axus0 = AxUS(input_dim=100, target_dim=10) + axus1 = deepcopy(axus0) + axus1._reset() + + self.assertEqual(axus0.input_dim, axus1.input_dim) + self.assertEqual(axus0.target_dim, axus1.target_dim) + self.assertFalse(np.allclose(axus0.S_prime, axus1.S_prime)) + self.assertFalse(np.allclose(axus0.S, axus1.S)) + + ttid0 = axus0.target_to_input_dim + ttid1 = axus1.target_to_input_dim + + all_equal = True + for t_dim in range(10): + np0 = np.array(ttid0[t_dim]) + np1 = np.array(ttid1[t_dim]) + if len(np0) != len(np1): + all_equal = False + break + elif not np.allclose(np0, np1): + all_equal = False + break + self.assertFalse(all_equal) + + def test_bin_distribution(self): + """ + Test that bins follow the desired almost equal distribution. + + Returns: + + """ + axus = AxUS(input_dim=14, target_dim=4) + non_zeros_per_row = [np.count_nonzero(axus.S[i]) for i in + range(4)] # there should be two bins with three elements and two bins with four elements + self.assertEqual(2, len([i for i in non_zeros_per_row if i == 3])) + self.assertEqual(2, len([i for i in non_zeros_per_row if i == 4])) + + def test_increase_embedding(self): + axus = AxUS(input_dim=10, target_dim=2) + row_to_split = axus.S[0].copy() + axus.increase_target_dimensionality(dims_and_bins={0: 3}) # should increase target dimensionality by 2 + self.assertEqual(axus.S.shape[0], 4) + self.assertEqual(axus.S_prime.shape[1], 4) + non_zero_elements_before_split = np.count_nonzero(row_to_split) + non_zero_in_new_rows = np.count_nonzero(axus.S[-1]) + np.count_nonzero(axus.S[-2]) + self.assertEqual(np.count_nonzero(axus.S[0]), non_zero_elements_before_split - non_zero_in_new_rows) + + self.assertEqual(10, np.count_nonzero(axus.S)) + self.assertEqual(10, np.count_nonzero(axus.S_prime)) diff --git a/mylib/lib_BAxUS/BAxUS/tests/test_synthetic_benchmark_functions.py b/mylib/lib_BAxUS/BAxUS/tests/test_synthetic_benchmark_functions.py new file mode 100644 index 0000000..abf3356 --- /dev/null +++ b/mylib/lib_BAxUS/BAxUS/tests/test_synthetic_benchmark_functions.py @@ -0,0 +1,274 @@ +import math +from unittest import TestCase + +import numpy as np +import pytest + +from baxus.benchmarks import AckleyEffectiveDim, RosenbrockEffectiveDim, LevyEffectiveDim, DixonPriceEffectiveDim, \ + BraninEffectiveDim, RastriginEffectiveDim, MichalewiczEffectiveDim, GriewankEffectiveDim, HartmannEffectiveDim, \ + RotatedHartmann6 +from baxus.util.exceptions import EffectiveDimTooLargeException + + +class SyntheticBenchmarkFunctionsTestSuite(TestCase): + def test_functions(self): + funs = [AckleyEffectiveDim, RosenbrockEffectiveDim, LevyEffectiveDim, DixonPriceEffectiveDim, + GriewankEffectiveDim, MichalewiczEffectiveDim, RastriginEffectiveDim, BraninEffectiveDim, + HartmannEffectiveDim, ] + for fun in funs: + fun_instance = fun() + assert fun_instance.dim == 200 + + fun_instance = fun(dim=164) + assert fun_instance.dim == 164 + fun_instance = fun(noise_std=3.0) + f1 = fun_instance(np.zeros(200)) + f2 = fun_instance(np.zeros(200)) + self.assertNotAlmostEqual(f1, f2, places=5) + funs = [BraninEffectiveDim, HartmannEffectiveDim, RosenbrockEffectiveDim] + for fun in funs: + fun_instance = fun() + assert fun_instance.dim == 200 + + fun_instance = fun(164) + assert fun_instance.dim == 164 + + fun_instance = fun(noise_std=3.0) + f1 = fun_instance(np.zeros(200)) + f2 = fun_instance(np.zeros(200)) + self.assertNotAlmostEqual(f1, f2, places=5) + + def test_return_multiple_values(self): + funs = [AckleyEffectiveDim, RosenbrockEffectiveDim, LevyEffectiveDim, DixonPriceEffectiveDim, + GriewankEffectiveDim, MichalewiczEffectiveDim, RastriginEffectiveDim, BraninEffectiveDim, + HartmannEffectiveDim, ] + for fun in funs: + fun_instance = fun() + y = fun_instance(np.zeros((3, 200))) + assert len(y) == 3 + assert np.allclose(y[0], y[1]) + assert np.allclose(y[1], y[2]) + + def test_ackley_optimum(self): + fun_instance = AckleyEffectiveDim() + self.assertAlmostEqual(fun_instance(np.zeros(200)), 0.0, places=5) + + def test_ackley_bounds(self): + fun_instance = AckleyEffectiveDim(47) + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=47, fill_value=-32.768)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=47, fill_value=32.768)) + ) + fun_instance = AckleyEffectiveDim() + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=200, fill_value=-32.768)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=200, fill_value=32.768)) + ) + + def test_rosenbrock_optimum(self): + fun_instance = RosenbrockEffectiveDim() + self.assertAlmostEqual(fun_instance(np.ones(200)), 0.0, places=5) + + def test_rosenbrock_bounds(self): + fun_instance = RosenbrockEffectiveDim(23) + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=23, fill_value=-5)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=23, fill_value=10)) + ) + fun_instance = RosenbrockEffectiveDim() + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=200, fill_value=-5)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=200, fill_value=10)) + ) + + def test_levy_optimum(self): + fun_instance = LevyEffectiveDim() + self.assertAlmostEqual(fun_instance(np.ones(200)), 0.0, places=5) + + def test_levy_bounds(self): + fun_instance = LevyEffectiveDim(34) + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=34, fill_value=-10)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=34, fill_value=10)) + ) + fun_instance = LevyEffectiveDim() + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=200, fill_value=-10)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=200, fill_value=10)) + ) + + def test_dixon_price_optimum(self): + fun_instance = DixonPriceEffectiveDim() + point = np.array([2 ** (-(2 ** i - 2) / 2 ** i) for i in range(1, 201)]) + self.assertAlmostEqual(fun_instance(point), 0.0, places=5) + + def test_dixon_price_bounds(self): + fun_instance = DixonPriceEffectiveDim(654) + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=654, fill_value=-10)) + ) + + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=654, fill_value=10)) + ) + fun_instance = DixonPriceEffectiveDim() + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=200, fill_value=-10)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=200, fill_value=10)) + ) + + def test_griewank_optimum(self): + fun_instance = GriewankEffectiveDim() + self.assertAlmostEqual(fun_instance(np.zeros(200)), 0.0, places=5) + + def test_griewank_price_bounds(self): + fun_instance = GriewankEffectiveDim(123) + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=123, fill_value=-600)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=123, fill_value=600)) + ) + fun_instance = GriewankEffectiveDim() + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=200, fill_value=-600)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=200, fill_value=600)) + ) + + def test_michalewicz_bounds(self): + fun_instance = MichalewiczEffectiveDim(76) + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=76, fill_value=0)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=76, fill_value=math.pi)) + ) + fun_instance = MichalewiczEffectiveDim() + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=200, fill_value=0)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=200, fill_value=math.pi)) + ) + + def test_rastrigin_optimum(self): + fun_instance = RastriginEffectiveDim() + self.assertAlmostEqual(fun_instance(np.zeros(200)), 0.0, places=5) + + def test_rastrigin_bounds(self): + fun_instance = RastriginEffectiveDim(54) + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=54, fill_value=-5.12)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=54, fill_value=5.12)) + ) + fun_instance = RastriginEffectiveDim() + self.assertTrue( + np.allclose(fun_instance.lb_vec, np.full(shape=200, fill_value=-5.12)) + ) + self.assertTrue( + np.allclose(fun_instance.ub_vec, np.full(shape=200, fill_value=5.12)) + ) + + def test_effective_dim_benchmarks(self): + hartmann6 = HartmannEffectiveDim() + assert hartmann6.effective_dim == 6 + assert hartmann6.dim == 200 + branin2 = BraninEffectiveDim() + assert branin2.effective_dim == 2 + assert branin2.dim == 200 + rb = RosenbrockEffectiveDim() + assert rb.effective_dim == 10 + assert rb.dim == 200 + rb = RosenbrockEffectiveDim(effective_dim=54) + assert rb.effective_dim == 54 + assert rb.dim == 200 + + funs = [HartmannEffectiveDim, BraninEffectiveDim, RosenbrockEffectiveDim] + for fun in funs: + f_inst = fun(dim=145) + assert f_inst.dim == 145 + + def test_rosenbrock_eff_lower_than_true_dim(self): + with pytest.raises(EffectiveDimTooLargeException): + RosenbrockEffectiveDim(dim=23, effective_dim=24) + + def test_branin2_eff_lower_than_true_dim(self): + with pytest.raises(EffectiveDimTooLargeException): + BraninEffectiveDim(dim=1) + + def test_hartmann6_eff_lower_than_true_dim(self): + with pytest.raises(EffectiveDimTooLargeException): + HartmannEffectiveDim(dim=2) + + +class RotatedHartmann6Test(TestCase): + def test_init(self): + benchmark = RotatedHartmann6() + self.assertEqual(benchmark.dim, 1000) + + def test_call_with_one_value_numpy_array(self): + benchmark = RotatedHartmann6() + input = np.random.uniform(low=benchmark.lb_vec, high=benchmark.ub_vec) + results = benchmark(input) + self.assertEqual(results.size, 1) + + def test_call_with_multiple_values_numpy_array(self): + benchmark = RotatedHartmann6() + input = np.random.uniform( + low=benchmark.lb_vec, high=benchmark.ub_vec, size=(5, benchmark.dim) + ) + results = benchmark(input) + self.assertEqual(len(results), 5) + + def test_call_with_one_value_list(self): + benchmark = RotatedHartmann6() + input = np.random.uniform(low=benchmark.lb_vec, high=benchmark.ub_vec).tolist() + results = benchmark(input) + self.assertEqual(results.size, 1) + + def test_call_with_multiple_values_numpy_array(self): + benchmark = RotatedHartmann6() + input = np.random.uniform( + low=benchmark.lb_vec, high=benchmark.ub_vec, size=(5, benchmark.dim) + ).tolist() + results = benchmark(input) + self.assertEqual(len(results), 5) + + # TODO fix these tests + # @expectedFailure + # def test_out_of_lower_bounds(self): + # benchmark = RotatedHartmann6() + # input = np.random.uniform( + # low=benchmark.lb_vec - 5.0, + # high=benchmark.lb_vec - 1.0, + # size=(5, benchmark.dim), + # ) + # benchmark(input) + + # @expectedFailure + # def test_out_of_upper_bounds(self): + # benchmark = RotatedHartmann6() + # input = np.random.uniform( + # low=benchmark.ub_vec + 1.0, + # high=benchmark.lb_vec + 5.0, + # size=(5, benchmark.dim), + # ) + # benchmark(input) diff --git a/mylib/lib_BO_torch_repo/Algorithms/AbstractAlgorithm.py b/mylib/lib_BO_torch_repo/Algorithms/AbstractAlgorithm.py new file mode 100644 index 0000000..0fa9e65 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/AbstractAlgorithm.py @@ -0,0 +1,304 @@ +r""" +utf-8 + +This is a handle to deal with the algorithm class to implement with Modular Problems +""" + +__author__ = ["Iván Olarte Rodríguez"] + +from typing import Union, Callable, List, Tuple, Optional +from abc import ABC, abstractmethod +from ioh.iohcpp.problem import RealSingleObjective, BBOB +from ioh.iohcpp import MAX +from ioh.iohcpp import RealBounds +from math import inf +import numpy as np +import warnings + +class AbstractAlgorithm(ABC): + + @abstractmethod + def __init__(self, **kwargs): + r""" + This is the constructor for any optimisation algorithm used within this framework. + In this framework, the initialiser might receive two keywords related to the + maximisation/minimisation beahviour and the verbosity. + """ + + # Initialize the number of function evaluations + self.__number_of_function_evaluations:int = 0 + + + # Set initial default variables + verbose_init = kwargs.pop("verbose",False) + maximisation_init = kwargs.pop("maximisation",False) + + # Initialise the bounds as Nonetype + self.__bounds:np.ndarray = np.empty(shape=(1,2)) + + + # Assign the variables as members of the class + self.verbose = verbose_init + self.__maximisation = maximisation_init + self.__current_best_index:int = 0 + + # Initialize a storage variable for the best sample + if self.__maximisation == True: + self.__current_best:float = -inf + else: + self.__current_best:float = inf + pass + + self.__problem = None + self.__dimension = None + + @abstractmethod + def __call__(self, + problem:Union[RealSingleObjective,BBOB, Callable], + dim:Optional[int], + bounds:Optional[np.ndarray], + **kwargs): + """ + This is a default function indicating the structure of the `_call_` method in the context of an algorithm + """ + if isinstance(problem,BBOB) or isinstance(problem,RealSingleObjective) or issubclass(type(problem),RealSingleObjective) : + + # Assign the problem + self.__problem = problem + # Get the dimensionality from this + self.dimension = problem.meta_data.n_variables + self.maximisation = (problem.meta_data.optimization_type==MAX) # This is a placeholder to modify + + # Pass the bounds from the IOH definition (the setter function will adapt the input) + self.bounds = problem.bounds + elif isinstance(problem,Callable): + # Assign the problem + self.__problem = problem + + # Set the dimension to be given by the parameters + self.dimension = dim + + # In case the a new maximisation default is given as a parameter + self.maximisation = kwargs.pop("maximisation",False) + + analyzed_bounds = bounds + if isinstance(analyzed_bounds,(np.ndarray, List[float], Tuple[float])): + # Assign in this case + self.bounds = analyzed_bounds + else: + raise AttributeError("The bounds for a callable problem were not found", name="bounds") + + else: + raise AttributeError("The problem input is not well defined", + name="problem", + obj=problem) + + pass + + @abstractmethod + def __str__(self): + pass + + def __repr__(self): + return super().__repr__() + + def __name__(self)->str: + " This is just to identify the name of the algorithm, which is included in the name" + return self.__class__.__name__ + + @abstractmethod + def reset(self): + # Set the number of function evaluations to 0 + self.number_of_function_evaluations = 0 + + if self.maximisation: + self.current_best = -inf + else: + self.current_best = inf + + self.__current_best_index = 0 + + pass + + + + @property + def number_of_function_evaluations(self)->int: + "This property handles the number of function evaluations" + return self.__number_of_function_evaluations + + @number_of_function_evaluations.setter + def number_of_function_evaluations(self, new_eval:int)->None: + "This is the setter for the number of function evaluations" + if isinstance(new_eval,int) and new_eval >= 0: + self.__number_of_function_evaluations = new_eval + else: + raise ValueError("The number of function evaluations must be a positive integer") + + + + @property + def verbose(self)->bool: + "Definition of verbosity of the algorithm" + return self.__verbose + + @verbose.setter + def verbose(self, new_verbosity:bool)->None: + "Rewrite the verbosity parameter" + self.__verbose = bool(new_verbosity) + + @property + def dimension(self)->Union[int,None]: + return self.__dimension + + @dimension.deleter + def dimension(self)->None: + del self.__dimension + + @dimension.setter + def dimension(self, new_dimension:Union[int,None])->None: + if isinstance(new_dimension,int) and new_dimension > 0: + self.__dimension = new_dimension + elif isinstance(new_dimension,None): + self.__dimension = new_dimension + else: + raise ValueError("The new dimension is oddly set") + + @property + def current_best(self)->float: + return self.__current_best + + @current_best.setter + def current_best(self, new_current_best:float): + if self.__maximisation: + if new_current_best >= self.__current_best: + # Assign this new value + self.__current_best = new_current_best + else: + raise ValueError("The assignment is incorrect") + else: + if new_current_best <= self.__current_best: + # Assign this new value + self.__current_best = new_current_best + else: + raise ValueError("The assignment is incorrect") + + @property + def current_best_index(self)->int: + return self.__current_best_index + + @current_best_index.setter + def current_best_index(self, new_current_best_index:int)->None: + if isinstance(new_current_best_index, int) and new_current_best_index >= self.__current_best_index: + # Assign in this case + self.__current_best_index = new_current_best_index + else: + raise ValueError("Something is wrong with this assignment") + + @property + def maximisation(self)->bool: + return self.__maximisation + + @maximisation.setter + def maximisation(self, new_definition:bool)->None: + # Assign the change in variable + if self.__maximisation != bool(new_definition): + self.__maximisation = bool(new_definition) + # Change given this condition + if self.__maximisation == True: + self.__current_best = -inf + else: + self.__current_best = inf + + @property + def bounds(self)->np.ndarray: + r""" + Bounds property `getter`. This just a repeater from the token in memory + """ + return self.__bounds + + @bounds.setter + def bounds(self, new_bounds:Union[np.ndarray,RealBounds,List[float], Tuple[float]]): + r""" + This is the bounds property setter + """ + + # The first case is if the bounds come from an IOH defined problem + if isinstance(new_bounds,RealBounds): + + lower_bounds:Union[np.ndarray,float] = new_bounds.lb + upper_bounds:Union[np.ndarray,float] = new_bounds.ub + + if isinstance(lower_bounds,float): + # this is in case the value is repeated per every dimension + lower_bounds_arr = np.ones(shape=(self.dimension,1))*lower_bounds + upper_bounds_arr = np.ones(shape=(self.dimension,1))*upper_bounds + + # Reshape the array + self.__bounds:np.ndarray = np.hstack(tup=(lower_bounds_arr,upper_bounds_arr)) + + elif isinstance(lower_bounds, np.ndarray): + + if lower_bounds.size == 1: + # this is in case the value is repeated per every dimension + lower_bounds_arr:np.ndarray = np.tile(lower_bounds,reps=self.dimension).reshape((self.dimension,1)) + upper_bounds_arr:np.ndarray = np.tile(upper_bounds,reps=self.dimension).reshape((self.dimension,1)) + + else: + lower_bounds_arr:np.ndarray = lower_bounds.reshape((self.dimension,1)) + upper_bounds_arr:np.ndarray = upper_bounds.reshape((self.dimension,1)) + + + # Reshape the array + self.__bounds:np.ndarray = np.hstack(tup=(lower_bounds_arr,upper_bounds_arr)) + + else: + + try: + new_bounds = np.array(new_bounds) + except Exception as e: + print("Error has occured", e.args, + flush=True ) + + if new_bounds.size == 2: + # This is the case all the bounds are the same + new_bounds = new_bounds.ravel() + + lower_bounds = new_bounds[0] + upper_bounds = new_bounds[1] + + lower_bounds_arr = np.ones(shape=(self.dimension,1))*lower_bounds + upper_bounds_arr = np.ones(shape=(self.dimension,1))*upper_bounds + + # Reshape the array + self.__bounds:np.ndarray = np.hstack(tup=(lower_bounds_arr,upper_bounds_arr)) + + elif np.remainder(new_bounds.size,2) == 0 and new_bounds.size > 2: + + new_bounds = new_bounds.reshape((-1,2)) + lower_bounds_arr = new_bounds[:,0].reshape((-1,1)) + upper_bounds_arr = new_bounds[:,1].reshape((-1,1)) + + # Reshape the array + self.__bounds:np.ndarray = np.hstack(tup=(lower_bounds_arr,upper_bounds_arr)) + + else: + raise AttributeError("The bounds should be a given in pairs", name="bounds") + + + def compute_space_volume(self)->float: + r""" + This function computes the volume of the space defined by the bounds + """ + # Compute the volume of the space + return np.prod(self.bounds[:,1]-self.bounds[:,0]) + + + + + + + + + + \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/AbstractBayesianOptimizer.py b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/AbstractBayesianOptimizer.py new file mode 100644 index 0000000..c1fe4e9 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/AbstractBayesianOptimizer.py @@ -0,0 +1,292 @@ +from ..AbstractAlgorithm import AbstractAlgorithm +from typing import Union, Optional, List +from pyDOE import lhs +import numpy as np +from ioh.iohcpp.problem import RealSingleObjective +from abc import abstractmethod + + + +class LHS_sampler(object): + + __default_criteria = ("center","maximin","centermaximin","correlation") + __reduced_criteria = {"c":"center", + "m":"maximin", + "cm":"centermaximin", + "corr":"correlation"} + + def __init__(self, + criterion:str="maximin", + iterations:int = 1000, + sample_zero:bool = False): + + self.criterion = criterion + self.iterations = iterations + self.sample_zero = sample_zero + + + def __call__(self, dim:int, n_samples:int, random_seed:int=43)->np.ndarray: + r""" + This `__call__` overload runs the LHS Experiment and returns a `NumPy array` + """ + + # Set the random seed + np.random.seed(random_seed) + + points:np.ndarray = lhs(n=dim, + samples=n_samples, + criterion=self.criterion, + iterations=self.iterations + ) + + if self.sample_zero: + points[0,:] = np.ones_like(points[0,:])*0.5 + + + + # Return the points transformed as a Tensor + return points.reshape((n_samples,dim)) + + + @property + def criterion(self)->str: + return self.__criterion + + @criterion.setter + def criterion(self, new_criterion:str)->None: + r""" + This property holder checks if the criterion is well defined + """ + + if not isinstance(new_criterion,str): + raise ValueError("The new criterion is not a string!") + + else: + # Lower case + new_criterion = new_criterion.lower().strip() + if new_criterion not in self.__default_criteria: + # Check if the criterion is in the reduced criterion + if new_criterion in [*self.__reduced_criteria]: + self.__criterion = self.__reduced_criteria[new_criterion] + else: + raise ValueError("The criterion is not matching the set ones!") + else: + self.__criterion = new_criterion + + + @property + def iterations(self)->int: + return self.__iterations + + @iterations.setter + def iterations(self,new_n_iter:int)->None: + if new_n_iter > 0: + self.__iterations = int(new_n_iter) + else: + raise ValueError("Negative iterations not allowed") + + @property + def sample_zero(self)->bool: + r""" + Property for sampling the zero vector + """ + return self.__sample_zero + + @sample_zero.setter + def sample_zero(self,new_change:bool)->None: + + try: + bool(new_change) + except Exception as e: + print(e.args) + + # set the new value + self.__sample_zero = new_change + +class AbstractBayesianOptimizer(AbstractAlgorithm): + + + def __init__(self, + budget:int, + n_DoE:Optional[int]=0, + random_seed:int = 43, + **kwargs): + + # call the initialiser from super class + super().__init__(**kwargs) + self.budget:int = budget + self.n_DoE:int = n_DoE + + DoE_parameters = None + for key,item in kwargs.items(): + if key.lower().strip() == "doe_parameters": + DoE_parameters = item + + + + full_parameters:dict = self.__build_LHS_parameters(DoE_parameters) + + + self.random_seed = random_seed + + # Check that there is some dictionary with the name "LHS_configuration" + self.__lhs_sampler:LHS_sampler = LHS_sampler(criterion=full_parameters['criterion'], + iterations=full_parameters['iterations'], + sample_zero=full_parameters['sample_zero']) + + + + # Store all the evaluations (x,y) + self.__x_evals:List[np.ndarray] = [] + self.__f_evals:List[float] = [] + + def __str__(self): + pass + + def __call__(self, problem, dim:int, bounds:np.ndarray,**kwargs)->None: + + # Call the superclass + super().__call__(problem, + dim, + bounds, + **kwargs) + + if not isinstance(self.n_DoE,int) or self.n_DoE==0: + # Assign this equal to the dimension of the problem + self.n_DoE = self.dimension + + # Sample the points + init_points:np.ndarray = self.lhs_sampler(self.dimension, + self.n_DoE,self.random_seed) + + # Rescale the initial points + init_points = self._rescale_lhs_points(init_points) + # perform a loop with each point + for _, point in enumerate(init_points): + # append the new points + self.__x_evals.append(point) + self.__f_evals.append(problem(point)) + + # Redefine the best + self.assign_new_best() + + self.number_of_function_evaluations = self.n_DoE + + # Print best to screen if verbose + if self.verbose: + print("After Initial sampling...", f"Current Best: x:{self.__x_evals[self.current_best_index]} y:{self.current_best}" + ,flush=True) + + pass + + def _rescale_lhs_points(self,raw_lhs_points:np.ndarray): + r""" + This function is defined in order to take the Latin Hypercube Samples + and project these points into the raw space defined by each dimension + + Args + ------- + - raw_lhs_points (`np.ndarray`): A NumPy array with the initial samples coming from DoE (some points between 0 to 1) + """ + + # Take a copy of the raw points + new_array:np.ndarray = np.empty_like(raw_lhs_points) + + # Perform a loop all over the dimensionality of the points + for dim in range(self.dimension): + + # Compute the multiplier + multiplier:float = self.bounds[dim,1] - self.bounds[dim,0] + new_array[:,dim] = multiplier*raw_lhs_points[:,dim] + self.bounds[dim,0] + + return new_array + + + + @abstractmethod + def assign_new_best(self): + if self.maximisation: + self.current_best = max(self.__f_evals) + + else: + self.current_best = min(self.__f_evals) + + # Assign the index + self.current_best_index = self.__f_evals.index(self.current_best, # Value + self.current_best_index #Starting search position + ) + + def __repr__(self): + return super(AbstractAlgorithm,self).__repr__() + + + def __build_LHS_parameters(self, params_dict:Union[dict,None])->dict: + r""" + This function builds the LHS parameters to initialize the optimisation + loop. + """ + + """ + samples=self.n_samples, + criterion=self.criterion, + iterations=self.iterations + """ + + complete_params_dict:dict = {"criterion":"center", + "iterations":1000, + "sample_zero":False} + + if isinstance(params_dict,dict): + for key, item in params_dict.items(): + complete_params_dict[key] = item + + return complete_params_dict + + def reset(self)->None: + + # Call the superclass reset + super().reset() + + # Reset the evaluations + self.__x_evals:List[np.ndarray] = [] + self.__f_evals:List[float] = [] + + @property + def budget(self)->int: + return self.__budget + + @budget.setter + def budget(self,new_budget:int)->None: + self.__budget = int(new_budget) if new_budget > 0 else None + + @property + def n_DoE(self)->int: + return self.__n_DoE + + @n_DoE.setter + def n_DoE(self, new_n_DOE:int)->None: + self.__n_DoE = int(new_n_DOE) if new_n_DOE >= 0 else None + + @property + def lhs_sampler(self)->LHS_sampler: + return self.__lhs_sampler + + + @property + def f_evals(self)->List[float]: + return self.__f_evals + + @property + def x_evals(self)->List[np.ndarray]: + return self.__x_evals + + @property + def random_seed(self)->int: + return self._random_seed + + @random_seed.setter + def random_seed(self,new_seed:int)->None: + if isinstance(new_seed,int) and new_seed >=0: + self._random_seed = new_seed + + diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/Vanilla_BO.py b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/Vanilla_BO.py new file mode 100644 index 0000000..c910fa0 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/Vanilla_BO.py @@ -0,0 +1,252 @@ +from ..AbstractBayesianOptimizer import AbstractBayesianOptimizer +from typing import Union, Callable, Optional +from ioh.iohcpp.problem import RealSingleObjective, BBOB +from pyDOE import lhs +from functools import partial +import numpy as np +import torch +import os +from torch import Tensor +from botorch.models import SingleTaskGP +from botorch.models.transforms.input import InputStandardize, Normalize +from botorch.acquisition.analytic import (ExpectedImprovement, + ProbabilityOfImprovement, + UpperConfidenceBound, + AnalyticAcquisitionFunction) +from botorch.optim import optimize_acqf +from botorch.models.transforms.outcome import Standardize +from gpytorch.likelihoods import GaussianLikelihood + +#from gpytorch.mlls.marginal_log + +ALLOWED_ACQUISITION_FUNCTION_STRINGS:tuple = ("expected_improvement", + "probability_of_improvement", + "upper_confidence_bound") + +ALLOWED_SHORTHAND_ACQUISITION_FUNCTION_STRINGS:dict = {"EI":"expected_improvement", + "PI":"probability_of_improvement", + "UCB":"upper_confidence_bound"} + + +class Vanilla_BO(AbstractBayesianOptimizer): + def __init__(self, budget, n_DoE=0, acquisition_function:str="expected_improvement", + random_seed:int=43,**kwargs): + + # Call the superclass + super().__init__(budget=budget, + n_DoE=n_DoE, + random_seed= random_seed, + **kwargs) + + # Check the defaults + device = torch.device("cuda:3" if torch.cuda.is_available() else "cpu") + dtype = torch.double + smoke_test = os.environ.get("SMOKE_TEST") + + # Set up the main configuration + self.__torch_config:dict = {"device":device, + "dtype":dtype, + "SMOKE_TEST":smoke_test, + "BATCH_SIZE":3 if not smoke_test else 2, + "NUM_RESTARTS": 10 if not smoke_test else 2, + "RAW_SAMPLES": 512 if not smoke_test else 32} + + # Set-up the acquisition function + self.__acq_func:Optional[Union[AnalyticAcquisitionFunction,Callable]] = None + self.acquistion_function_name = acquisition_function + + + def __str__(self): + return "This is an instance of Vanilla BO Optimizer" + + def __call__(self, problem:Union[RealSingleObjective,Callable], + dim:Optional[int]=-1, + bounds:Optional[np.ndarray]=None, + **kwargs)-> None: + + # Call the superclass to run the initial sampling of the problem + super().__call__(problem, dim, bounds, **kwargs) + + # Get a default beta (for UCB) + beta = kwargs.pop("beta",0.2) + + # Run the model initialisation + self._initialise_model(**kwargs) + + # Start the optimisation loop + for cur_iteration in range(self.budget-self.n_DoE): + + # Set up the acquistion function + self.acquisition_function = self.acquisition_function_class( + model= self.__model_obj, + best_f= self.current_best, + maximize= self.maximisation, + beta=beta, + kwargs=kwargs + ) + + new_x = self.optimize_acqf_and_get_observation() + + # Append the new values + for _, new_x_arr in enumerate(new_x): + new_x_arr_numpy:np.ndarray = new_x_arr.detach().numpy().ravel() + + # Append the new value + self.x_evals.append(new_x_arr_numpy) + + # Evaluate the function + new_f_eval:float = problem(new_x_arr_numpy) + + # Append the function evaluation + self.f_evals.append(new_f_eval) + + # Increment the number of function evaluations + self.number_of_function_evaluations +=1 + + # Assign the new best + self.assign_new_best() + + # Print best to screen if verbose + if self.verbose: + print(f"Current Iteration:{cur_iteration+1}", + f"Current Best: x:{self.x_evals[self.current_best_index]} y:{self.current_best}" + ,flush=True) + + + # Re-fit the GPR + self._initialise_model() + + print("Optimisation Process finalised!") + + def assign_new_best(self): + # Call the super class + super().assign_new_best() + + + def _initialise_model(self, **kwargs): + r""" + This function initialise/fits the Gaussian Process Regression + + + Args: + ------- + - **kwargs: Left these keyword arguments for upcoming developments + """ + + # Convert bounds array to Torch + #bounds_torch:Tensor = torch.from_numpy(self.bounds.transpose()).detach() + + # Convert the initial values to Torch Tensors + train_x:np.ndarray = np.array(self.x_evals).reshape((-1,self.dimension)) + train_x:Tensor = torch.from_numpy(train_x).detach() + + train_obj:np.ndarray = np.array(self.f_evals).reshape((-1,1)) + train_obj:Tensor = torch.from_numpy(train_obj).detach() + + self.__model_obj:SingleTaskGP = SingleTaskGP(train_x, + train_obj, + #train_Yvar, + #likelihood=GaussianLikelihood(noise_prior=1e-06), + outcome_transform=Standardize(m=1,min_stdv=5e-06), + #input_transform=Normalize(d=train_x.shape[-1], + # bounds=bounds_torch) + ) + + def optimize_acqf_and_get_observation(self)->Tensor: + """Optimizes the acquisition function, and returns a new candidate.""" + # optimize + candidates, _ = optimize_acqf( + acq_function=self.acquisition_function, + bounds=torch.from_numpy(self.bounds.transpose()).detach(), + q=1,#self.__torch_config['BATCH_SIZE'], + num_restarts=self.__torch_config['NUM_RESTARTS'], + raw_samples=self.__torch_config['RAW_SAMPLES'], # used for intialization heuristic + options={"batch_limit": 20, "maxiter": 2000}, + sequential=True, + return_best_only=True + ) + + + # observe new value + new_x = candidates.detach() + + new_x = new_x.reshape(shape=((1,-1))).detach() + + + + return new_x + + + def __repr__(self): + return super().__repr__() + + def reset(self): + return super().reset() + + @property + def torch_config(self)->dict: + return self.__torch_config + + @property + def acquistion_function_name(self)->str: + return self.__acquisition_function_name + + @acquistion_function_name.setter + def acquistion_function_name(self, new_name:str)->None: + + # Remove some spaces + new_name = new_name.strip() + + # Start with a dummy variable + dummy_var:str = "" + + # Check in the reduced + if new_name in [*ALLOWED_SHORTHAND_ACQUISITION_FUNCTION_STRINGS]: + # Assign the name + dummy_var = ALLOWED_SHORTHAND_ACQUISITION_FUNCTION_STRINGS[new_name] + else: + if new_name.lower() in ALLOWED_ACQUISITION_FUNCTION_STRINGS: + dummy_var = new_name + else: + raise ValueError("Oddly defined name") + + self.__acquisition_function_name = dummy_var + # Run to set up the acquisition function subclass + self.set_acquisition_function_subclass() + + def set_acquisition_function_subclass(self)->None: + + if self.__acquisition_function_name == ALLOWED_ACQUISITION_FUNCTION_STRINGS[0]: + self.__acq_func_class = ExpectedImprovement + elif self.__acquisition_function_name == ALLOWED_ACQUISITION_FUNCTION_STRINGS[1]: + self.__acq_func_class = ProbabilityOfImprovement + elif self.__acquisition_function_name == ALLOWED_ACQUISITION_FUNCTION_STRINGS[2]: + self.__acq_func_class = UpperConfidenceBound + + @property + def acquisition_function_class(self)->Callable: + return self.__acq_func_class + + @property + def acquisition_function(self)->AnalyticAcquisitionFunction: + """ + This returns the stored defined acquisition function defined at some point + of the loop + """ + return self.__acq_func + + @acquisition_function.setter + def acquisition_function(self, new_acquisition_function:AnalyticAcquisitionFunction)->None: + r""" + This is the setter function to the new acquisition function + """ + + if issubclass(type(new_acquisition_function),AnalyticAcquisitionFunction): + # Assign in this case + self.__acq_func = new_acquisition_function + else: + raise AttributeError("Cannot assign the acquisition function as this does not inherit from the class `AnalyticAcquisitionFunction` ", + name="acquisition_function", + obj=self.__acq_func) + + diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/Vanilla_BO_Combinatorial.py b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/Vanilla_BO_Combinatorial.py new file mode 100644 index 0000000..26ade97 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/Vanilla_BO_Combinatorial.py @@ -0,0 +1,233 @@ +from .Vanilla_BO import Vanilla_BO +from IOH_Wrappers.ModularBBOBProblem import ModularBBOBProblem +from IOH_Wrappers.ModularNonBBOBProblem import ModularNonBBOBProblem +from typing import Union, Callable, Optional, List, Tuple +from ioh.iohcpp.problem import RealSingleObjective, BBOB +from pyDOE import lhs +from functools import partial +import numpy as np +import torch +import os +from torch import Tensor +from botorch.models import SingleTaskGP +from botorch.models.transforms.input import InputStandardize, Normalize +from botorch.acquisition.analytic import ExpectedImprovement, ProbabilityOfImprovement, UpperConfidenceBound, AnalyticAcquisitionFunction +from botorch.optim import optimize_acqf +from botorch.models.transforms.outcome import Standardize +from gpytorch.likelihoods import GaussianLikelihood +from gpytorch.constraints import GreaterThan + +#from gpytorch.mlls.marginal_log + +ALLOWED_ACQUISITION_FUNCTION_STRINGS:tuple = ("expected_improvement", + "probability_of_improvement", + "upper_confidence_bound") + +ALLOWED_SHORTHAND_ACQUISITION_FUNCTION_STRINGS:dict = {"EI":"expected_improvement", + "PI":"probability_of_improvement", + "UCB":"upper_confidence_bound"} + + +class Vanilla_BO_Combinatorial(Vanilla_BO): + """ + This is a handler class to perform the Vanilla BO with combinatorial repetitions of the target + + """ + def __init__(self, budget, n_DoE=0, random_seed=43,acquisition_function:str="expected_improvement",**kwargs): + + # Call the superclass + super().__init__(budget, + n_DoE, + acquisition_function, + random_seed, + **kwargs) + + # Initialise the intrinsic dimension + self.intrinsic_dimension:Union[int,None] = None + + + + + def __str__(self): + return "This is an instance of Vanilla BO Optimizer" + + def __call__(self, problem:Union[RealSingleObjective,BBOB,ModularBBOBProblem,ModularNonBBOBProblem,Callable], + dim:Optional[int]=-1, + bounds:Optional[np.ndarray]=None, + intrinsic_dimension:Optional[int] = None, + **kwargs)-> None: + + # Call the superclass (namely the AbstractBayesianOptimizer) to run the initial sampling of the problem + super(Vanilla_BO,self).__call__(problem, dim, bounds, **kwargs) + + if isinstance(problem,(ModularBBOBProblem,ModularNonBBOBProblem)): + self.intrinsic_dimension = problem.meta_data.latent_dimensionality + else: + # In this case the intrinsic dimension should be given + if isinstance(intrinsic_dimension,int) and np.remainder(self.dimension,intrinsic_dimension) == 0: + self.intrinsic_dimension = intrinsic_dimension + else: + raise AttributeError("The intrinsic dimension cannot be assigned as this is not a multiple of the global problem dimension", + name="intrinsic dimension", + obj=intrinsic_dimension) + + + # Get a default beta (for UCB) + beta = kwargs.pop("beta",0.2) + + # Run the model initialisation + self._initialise_model(**kwargs) + + # Start the optimisation loop + for cur_iteration in range(self.budget-self.n_DoE): + + # Set up the acquistion function + self.acquisition_function = self.acquisition_function_class( + model= self.__model_obj, + best_f= self.current_best, + maximize= self.maximisation, + beta=beta, + kwargs=kwargs + ) + + new_x = self.optimize_acqf_and_get_observation() + + # Append the new values + for _, new_x_arr in enumerate(new_x): + new_x_arr_numpy:np.ndarray = new_x_arr.detach().numpy().ravel() + + # Append the new value + self.x_evals.append(new_x_arr_numpy) + + # Evaluate the function + new_f_eval:float = problem(new_x_arr_numpy) + + # Append the function evaluation + self.f_evals.append(new_f_eval) + + # Increment the number of function evaluations + self.number_of_function_evaluations +=1 + # Assign the new best + self.assign_new_best() + + # Print best to screen if verbose + if self.verbose: + print(f"Current Iteration:{cur_iteration+1}", + f"Current Best: x:{self.x_evals[self.current_best_index]} y:{self.current_best}" + ,flush=True) + + + # Re-fit the GPR + self._initialise_model() + + # Print Message + print("Optimisation Process finalised!") + + def assign_new_best(self): + # Call the super class + super().assign_new_best() + + + def _initialise_model(self, **kwargs): + r""" + This function initialise/fits the Gaussian Process Regression + + + Args: + ------- + + - **kwargs: Left these keyword arguments for upcoming developments + """ + + import math + import itertools + + # Convert bounds array to Torch + bounds_torch:Tensor = torch.from_numpy(self.bounds.transpose()).detach() + + # Convert the arrays first to Numpy + train_x:np.ndarray = np.array(self.x_evals).reshape((-1,self.dimension)) + train_obj:np.ndarray = np.array(self.f_evals).reshape((-1,1)) + + # Arrange Space to augment the arrays + train_x_aug:List[np.ndarray] = [] + train_obj_aug:List[float] = [] + + main_single_array:np.ndarray = np.arange(int(self.dimension/self.intrinsic_dimension)) + + + + for idx,cur_arr in enumerate(train_x): + + # reshape current iterated array + main_struct_arr:np.ndarray = cur_arr.reshape((-1, + self.intrinsic_dimension)) + + cur_f_eval = train_obj.ravel()[idx] + + # Now start with a permutation approach + permutations_array:list = itertools.permutations(main_single_array.ravel().tolist()) + for cur_per in permutations_array: + modified_structure:np.ndarray = main_struct_arr[cur_per,:] + evaluation_array:np.ndarray = modified_structure.ravel(order="C") + + # Store in augmented list + train_x_aug.append(evaluation_array) + + train_obj_aug.append(cur_f_eval) + + # Now convert the augmented arrays to Torch + train_x_aug_torch:Tensor = torch.from_numpy(np.array(train_x_aug).reshape((-1,self.dimension))).detach() + train_obj_aug_torch:Tensor = torch.from_numpy(np.array(train_obj_aug).reshape((-1,1))).detach() + + likelihood = GaussianLikelihood(noise_constraint=GreaterThan(1e-07)) + + + self.__model_obj:SingleTaskGP = SingleTaskGP(train_x_aug_torch, + train_obj_aug_torch, + #train_Yvar, + likelihood=likelihood, + outcome_transform=Standardize(m=1), + input_transform=Normalize(d=train_x.shape[-1], + bounds=bounds_torch) + ) + + def optimize_acqf_and_get_observation(self)->Tensor: + """Optimizes the acquisition function, and returns a new candidate.""" + # Recycle the super_class + new_x = super().optimize_acqf_and_get_observation() + + return new_x + + + def __repr__(self): + return super().__repr__() + + def reset(self): + # call the Super Class + super().reset() + + # Assign the intrinsic dimension to None + self.intrinsic_dimension = None + + @property + def intrinsic_dimension(self)->Union[int,None]: + return self.__intrinsic_dimension + + @intrinsic_dimension.setter + def intrinsic_dimension(self, new_intrinsic_dimension:Union[int,None]): + + if isinstance(new_intrinsic_dimension,int) and new_intrinsic_dimension >= 1: + # Set the intrinsic dimension in this conditions + self.__intrinsic_dimension = new_intrinsic_dimension + elif new_intrinsic_dimension is None: + self.__intrinsic_dimension = new_intrinsic_dimension + else: + raise AttributeError("Cannot assign the intrinsic dimension to the given value", + name="intrinsic_dimension", + obj= new_intrinsic_dimension) + + + + + diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO.cpython-310.pyc new file mode 100644 index 0000000..282b409 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO.cpython-311.pyc b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO.cpython-311.pyc new file mode 100644 index 0000000..215ab93 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO_Combinatorial.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO_Combinatorial.cpython-310.pyc new file mode 100644 index 0000000..74b8988 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/Vanilla_BO/__pycache__/Vanilla_BO_Combinatorial.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/__pycache__/AbstractBayesianOptimizer.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/__pycache__/AbstractBayesianOptimizer.cpython-310.pyc new file mode 100644 index 0000000..9cdb8ee Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/__pycache__/AbstractBayesianOptimizer.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/__pycache__/AbstractBayesianOptimizer.cpython-311.pyc b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/__pycache__/AbstractBayesianOptimizer.cpython-311.pyc new file mode 100644 index 0000000..f83dc6d Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/BayesianOptimization/__pycache__/AbstractBayesianOptimizer.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Pure_CMA_ES.py b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Pure_CMA_ES.py new file mode 100644 index 0000000..2f71c1e --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Pure_CMA_ES.py @@ -0,0 +1,470 @@ +r""" + This module stores the different wrappers for CMA-ES. + This is an extension of Nikolaus Hansen's et. al. library to perform CMA-ES + and work within this framework +""" + +### -------------------------------------- +### MODULE PROPERTIES +### -------------------------------------- + +__author__ = ["Iván Olarte Rodríguez"] + + +import numpy as np # Import Numpy +from ..AbstractAlgorithm import AbstractAlgorithm +from cma import fmin +from cma import CMAOptions, CMAEvolutionStrategy +from cma.utilities import utils +from cma.options_parameters import safe_str +from cma import optimization_tools as ot +from typing import Union, Callable, List, Optional +from ioh.iohcpp.problem import RealSingleObjective +import warnings +import time + + +class Pure_CMA_ES(AbstractAlgorithm): + + def __init__(self, + x0:Union[np.ndarray,Callable], + sigma0:float, + budget:int, + inopts:Optional[Union[CMAOptions,dict]]=None, + random_seed:Optional[int]=42, + **kwargs): + r""" This is the extended constructor for the pure CMA_ES. + + Args: + ----------- + - x0: An initial point to start with CMA-ES + - sigma0: The initial step size + - budget: Number of maximum number of evaluations performed to the problem + - inopts: The initial options required for CMA-ES + - **kwargs: keyword arguments to pass to superclasses + """ + + # Use the superclass from the Abstract Algorithm + super().__init__(**kwargs) + + # Set the parameters of initial evaluation point and step sizer + self.x0 = x0 + self.sigma0 = sigma0 + + # Store the budget as some member variable + self.budget:int = budget + + # Store the random seed + self.random_seed:int = random_seed + + # Set the options for the CMA-ES + self.inopts = inopts + + # Initialise the number of iterations + self.iterations:int = 0 + + + def __str__(self): + return "Pure CMA-ES Optimizer" + + + def __call__(self, + problem, + dim:Optional[int]=-1, + bounds:Optional[np.ndarray]=None, + **kwargs)->None: + + # Call the Abstract superclass to check on the problem type and + # set the dimension and bounds + super().__call__(problem, dim, bounds, **kwargs) + + # Get definitions from kwargs + restarts:int = kwargs.pop("restarts",0) + restart_from_best:str = kwargs.pop("restart_from_best","False") + incpopsize:int = kwargs.pop("incpopsize",2) + bipop:bool = kwargs.pop("bipop",False) + callback:Callable = kwargs.pop("callback",None) + noise_kappa_exponent = kwargs.pop("noise_kappa_exponent",None) + eval_initial_x:bool = kwargs.pop("eval_initial_x",True) + + + + # Adjust the bounds setting to be fed into the definition of Hansen's CMA-ES + h_bounds = [self.bounds[:,0].ravel().tolist(), self.bounds[:,1].ravel().tolist()] + + # set the bounds into the options + self.inopts.set("bounds",h_bounds) + + + # Adjust the verbosity + if self.verbose: + self.inopts.set("verbose",1) + + # Check the dimensionality and x0 compatibility + if not self.x0.size == self.dimension: + # Adjust the dimension and warn the user + warnings.warn(f"The dimension of the initial point is different from the problem dimension." \ + "Adjusting the dimension to {self.dimension}") + if self.dimension < self.x0.size: + self.x0 = np.array(self.x0[:self.dimension]) + else: + self.x0 = np.hstack([self.x0,np.zeros(self.dimension-self.x0.size)]) + + # Set the options from the saved configuration + opts = self.inopts + ###-------------------------------------- + ### This is a direct copy from fmin method + ###-------------------------------------- + + if 1 < 3: # try: # pass on KeyboardInterrupt + + fmin_options = locals().copy() # archive original options + del fmin_options['problem'] + del fmin_options['dim'] + del fmin_options['kwargs'] + del fmin_options['bounds'] + del fmin_options['self'] + + + if callback is None: + callback = [] + elif callable(callback): + callback = [callback] + + # BIPOP-related variables: + runs_with_small = 0 + small_i = [] + large_i = [] + popsize0 = None # to be evaluated after the first iteration + maxiter0 = None # to be evaluated after the first iteration + base_evals = 0 + + irun = 0 + best = ot.BestSolution() + all_stoppings = [] + while True: # restart loop + sigma_factor = 1 + + # Adjust the population according to BIPOP after a restart. + if not bipop: + # BIPOP not in use, simply double the previous population + # on restart. + if irun > 0: + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + + elif irun == 0: + # Initial run is with "normal" population size; it is + # the large population before first doubling, but its + # budget accounting is the same as in case of small + # population. + poptype = 'small' + + elif sum(small_i) < sum(large_i): + # An interweaved run with small population size + poptype = 'small' + if 11 < 3: # not needed when compared to irun - runs_with_small + restarts += 1 # A small restart doesn't count in the total + runs_with_small += 1 # _Before_ it's used in popsize_lastlarge + + sigma_factor = 0.01**np.random.uniform() # Local search + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = np.floor(popsize0 * popsize_multiplier**(np.random.uniform()**2)) + opts['maxiter'] = min(maxiter0, 0.5 * sum(large_i) / opts['popsize']) + # print('small basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + else: + # A run with large population size; the population + # doubling is implicit with incpopsize. + poptype = 'large' + + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + opts['maxiter'] = maxiter0 + # print('large basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + + if irun and eval(str(fmin_options['restart_from_best'])): + utils.print_warning('CAVE: restart_from_best is often not useful', + verbose=opts['verbose']) + es = CMAEvolutionStrategy(best.x, sigma_factor * self.sigma0, opts) + else: + # Perform the sorting function before inserting the initial point + + + if irun==0: + x0 = self.x0.ravel() + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + else: + rng = np.random.default_rng(opts['seed']) + x0 = rng.uniform(0,1,self.dimension).ravel() + + for idx_,elem in enumerate(x0): + x0[idx_] = self.bounds[idx_,0] + elem*(self.bounds[idx_,1]-self.bounds[idx_,0]) + + # Start up the CMA-ES Evolution Strategy + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + + # return opts, es + if callable(problem) and ( + eval_initial_x + or es.opts['CMA_elitist'] == 'initial' + or (es.opts['CMA_elitist'] and + eval_initial_x is None)): + + + x = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + es.f0 = problem(x) + es.best.update([x], es.sent_solutions, + [es.f0], 1) + es.countevals += 1 + es.objective_function = problem # only for the record + + opts = es.opts # processed options, unambiguous + # a hack: + fmin_opts = CMAOptions("unchecked", **fmin_options.copy()) + for k in fmin_opts: + # locals() cannot be modified directly, exec won't work + # in 3.x, therefore + fmin_opts.eval(k, loc={'N': es.N, + 'popsize': opts['popsize']}, + correct_key=False) + + es.logger.append = opts['verb_append'] or es.countiter > 0 or irun > 0 + # es.logger is "the same" logger, because the "identity" + # is only determined by the `verb_filenameprefix` option + logger = es.logger # shortcut + try: + logger.persistent_communication_dict.update( + {'variable_annotations': + problem.variable_annotations}) + except AttributeError: + pass + + + # Set noise handling + noisehandler = ot.NoiseHandler(es.N, 0) # switched off + noise_handling = False + es.noise_handler = noisehandler + + + while not es.stop(): # iteration loop + # X, fit = eval_in_parallel(lambda: es.ask(1)[0], es.popsize, args, repetitions=noisehandler.evaluations-1) + X, fit = es.ask_and_eval(problem, + args=[], gradf=None, + evaluations=noisehandler.evaluations, + aggregation=np.median, + parallel_mode=False) # treats NaN with resampling if not parallel_mode + # TODO: check args and in case use args=(noisehandler.evaluations, ) + + # if es.opts['verbose'] > 4: # may be undesirable with dynamic fitness (e.g. Augmented Lagrangian) + # if es.countiter < 2 or min(fit) <= es.best.last.f: + # degrading_iterations_count = 0 # comes first to avoid code check complaint + # else: # min(fit) > es.best.last.f: + # degrading_iterations_count += 1 + # if degrading_iterations_count > 4: + # utils.print_message('%d f-degrading iterations (set verbose<=4 to suppress)' + # % degrading_iterations_count, + # iteration=es.countiter) + + + + es.tell(X, fit, check_points=None) # prepare for next iteration + for f in callback: + f is None or f(es) + es.disp() + logger.add(# more_data=[noisehandler.evaluations, 10**noisehandler.noiseS] if noise_handling else [], + modulo=1 if es.stop() and logger.modulo else None) + # if (opts['verb_log'] and opts['verb_plot'] and + # (es.countiter % max(opts['verb_plot'], opts['verb_log']) == 0 or es.stop())): + # logger.plot(324) + + # end while not es.stop + if opts['eval_final_mean'] and callable(problem): + mean_pheno = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + fmean = problem(mean_pheno) + es.countevals += 1 + es.best.update([mean_pheno], es.sent_solutions, [fmean], es.countevals) + + best.update(es.best, es.sent_solutions) # in restarted case + # es.best.update(best) + + this_evals = es.countevals - base_evals + base_evals = es.countevals + + # BIPOP stats update + + if irun == 0: + popsize0 = opts['popsize'] + maxiter0 = opts['maxiter'] + # XXX: This might be a bug? Reproduced from Matlab + # small_i.append(this_evals) + + if bipop: + if poptype == 'small': + small_i.append(this_evals) + else: # poptype == 'large' + large_i.append(this_evals) + + # final message + if opts['verb_disp']: + es.result_pretty(irun, time.asctime(time.localtime()), + best.f) + + irun += 1 + # if irun > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + # if irun > restarts or 'ftarget' in es.stop() \ + all_stoppings.append(dict(es.stop(check=False))) # keeping the order + if irun - runs_with_small > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + or 'maxfevals' in es.stop(check=False) or 'callback' in es.stop(check=False): + break + opts['verb_append'] = es.countevals + opts['popsize'] = fmin_opts['incpopsize'] * es.sp.popsize # TODO: use rather options? + try: + opts['seed'] += 1 + except TypeError: + pass + + # while irun + + # es.out['best'] = best # TODO: this is a rather suboptimal type for inspection in the shell + if irun: + es.best.update(best) + # TODO: there should be a better way to communicate the overall best + + # Assign the number of iterations and number of function evaluations to the class + self.iterations += es.countiter + self.number_of_function_evaluations = es.countevals + return es.result + (es.stop(), es, logger) + ### 4560 + # TODO refine output, can #args be flexible? + # is this well usable as it is now? + else: # except KeyboardInterrupt: # Exception as e: + if eval(safe_str(opts['verb_disp'])) > 0: + print(' in/outcomment ``raise`` in last line of cma.fmin to prevent/restore KeyboardInterrupt exception') + raise KeyboardInterrupt # cave: swallowing this exception can silently mess up experiments, if ctrl-C is hit + + + + # Call the fmin2 function directly + # res = fmin( + # objective_function=problem, + # x0=self.x0, + # sigma0=self.sigma0, + # options = self.inopts, + # eval_initial_x=True, + # restart_from_best=restart_from_best, + # restarts=restarts, + # incpopsize=incpopsize, + # bipop=bipop, + # callback=callback, + # noise_kappa_exponent=noise_kappa_exponent + # ) + + # Set the best value + #self.number_of_function_evaluations = res[3] + #self.iterations = res[4] + + + @property + def budget(self)->int: + return self.__budget + + @budget.setter + def budget(self, new_budget:int)->None: + if isinstance(new_budget,int) and new_budget > 0: + self.__budget:int = new_budget + else: + raise ValueError("The budget must be an integer greater than zero!") + + + @property + def x0(self)->np.ndarray: + return self.__x0 + + @x0.setter + def x0(self,new_x0:Union[List[float],np.ndarray]): + if isinstance(new_x0,list) or isinstance(new_x0,np.ndarray): + # Convert the list to array + new_x0 = np.array(new_x0).ravel() + + if self.dimension is None: + self.dimension = new_x0.size + + # Assign the new value + self.__x0 = new_x0 + else: + raise AttributeError(f"The argument {new_x0} is not of class `np.ndarray` or a List of `float`") + + + @property + def sigma0(self)->float: + return self.__sigma0 + + @sigma0.setter + def sigma0(self, new_sigma_0:float)->None: + if isinstance(new_sigma_0,(float,int)) and new_sigma_0 > 0: + self.__sigma0 = new_sigma_0 + else: + raise AttributeError + + @property + def inopts(self)->CMAOptions: + return self.__inopts + + @inopts.setter + def inopts(self, new_inopts:Union[dict,CMAOptions,list])->None: + if new_inopts is None or (isinstance(new_inopts,list) and len(new_inopts)==0 ): + self.__inopts = CMAOptions("unchecked",new_inopts).complement() + elif isinstance(new_inopts,dict): + self.__inopts = CMAOptions("unchecked",**new_inopts.copy()).complement() + + elif isinstance(new_inopts,CMAOptions): + self.__inopts = new_inopts.complement() + + else: + raise AttributeError("The passed update is not a dictionary, empty list or ``CMAOptions` ") + + # Set the maximum number of iterations + self.__inopts.set("maxfevals",self.budget) + + # Set the random seed + self.__inopts.set("seed",self.random_seed) + + # Do not set to evaluate the final mean + self.__inopts.set("eval_final_mean",False) + + @property + def random_seed(self)->int: + return self.__random_seed + + @random_seed.setter + def random_seed(self, new_random_seed:int)->None: + if isinstance(new_random_seed,int): + self.__random_seed = new_random_seed + else: + raise AttributeError("The random seed must be an integer") + + + @property + def iterations(self)->int: + return self.__iterations + + @iterations.setter + def iterations(self, new_iterations:int)->None: + if isinstance(new_iterations,int) and new_iterations >= 0: + self.__iterations = new_iterations + else: + raise AttributeError("The number of iterations must be an integer greater than zero") + + def reset(self): + # Call the reset method from superclass + super().reset() + self.iterations:int = 0 + + + \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Pure_CMA_ES_Tabu.py b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Pure_CMA_ES_Tabu.py new file mode 100644 index 0000000..d875b5a --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Pure_CMA_ES_Tabu.py @@ -0,0 +1,780 @@ +r""" + This module stores the different wrappers for CMA-ES. + This is an extension of Nikolaus Hansen's et. al. library to perform CMA-ES + and work within this framework. + + It appends the work from De Nobel et. al. (2024) in regards of Tabu Areas for + restarts. See "Avoiding Redundant Restarts in Multimodal Global Optimization". +""" + +### -------------------------------------- +### MODULE PROPERTIES +### -------------------------------------- + +__author__ = ["Iván Olarte Rodríguez"] + + +import numpy as np # Import Numpy +from ..AbstractAlgorithm import AbstractAlgorithm +from cma import CMAOptions, CMAEvolutionStrategy +from cma.utilities import utils +from cma.options_parameters import safe_str +from cma import optimization_tools as ot +from .Pure_CMA_ES import Pure_CMA_ES +from typing import Union, Callable, List, Optional, Tuple +from ioh.iohcpp.problem import RealSingleObjective +from ..utils.utilities import hill_valley_test, hill_valley_test_2 +from itertools import permutations +from scipy.special import gamma as gamma_func +import warnings +import time + + +class Pure_CMA_ES_Tabu(Pure_CMA_ES): + + def __init__(self, + x0:Union[np.ndarray,Callable], + sigma0:float, + budget:int, + inopts:Optional[Union[CMAOptions,dict]]=None, + random_seed:Optional[int]=42, + **kwargs): + r""" This is the extended constructor for the pure CMA_ES with Tabu Regions. + + Args: + ----------- + - x0: An initial point to start with CMA-ES + - sigma0: The initial step size + - budget: Number of maximum number of evaluations performed to the problem + - inopts: The initial options required for CMA-ES + - **kwargs: keyword arguments to pass to superclasses + """ + + # Use the superclass from Pure_CMA_ES class + super().__init__(x0,sigma0,budget,inopts,random_seed,**kwargs) + + # Initialize the tabu triplets + self.__tabu_triplets = [] + + + def __str__(self): + return "Pure CMA-ES Optimizer with Tabu zones implementation" + + + def __call__(self, + problem:Union[RealSingleObjective,Callable], + dim:Optional[int]=-1, + bounds:Optional[np.ndarray]=None, + shrinkage_factor:Optional[float]=0.5, + intrinsic_dimension:Optional[int] = None, + rejection_factor_c:Optional[float] = 200.0, + n_evals_hill_valley:Optional[int] = 10, + **kwargs)->None: + + r""" + This is the modified `__call__` function to implement the + CMA-ES Algorithm with Tabu Points restarts. + + Args: + ------------ + - problem: `Callable`: A callable object (preferably inheriting from IOH), to evaluate a single objective function. + - dim: `Optional[int]`: An indicator of the dimension of the problem. + - bounds: `Optional[np.ndarray]`: An array with the bounds of the problem. + - intrinsic_dimension: `Optional[int]`: An integer denoting the intrinsic dimension of the problem + i.e. The symmetries of the problem. + - shrinkage_factor: `Optional[float]`: The shrinkage factor to be applied to the rejection radius. + - rejection_factor_c: `Optional[float]`: The rejection factor to be applied to the rejection radius. + - n_evals_hill_valley: `Optional[int]`: The number of evaluations to perform in the Hill Valley Test. + """ + + # Call the Abstract superclass to check on the problem type and + # set the dimension and bounds + super(Pure_CMA_ES,self).__call__(problem, dim, bounds, **kwargs) + + # Ensure the shrinkage factor is between bounds + if not (shrinkage_factor > 0 and shrinkage_factor < 1): + raise ValueError("The shrinkage factor must be between 0 and 1") + + # Check the intrinsic dimension + if not ((isinstance(intrinsic_dimension,int) and intrinsic_dimension > 0) or intrinsic_dimension is None): + raise ValueError("The intrinsic dimension must be a positive integer or None") + + # Check the rejection factor + if not (rejection_factor_c > 0): + raise ValueError("The rejection factor must be a positive float") + + # Check the number of evaluations in the Hill Valley Test + + if not (n_evals_hill_valley > 0 or isinstance(n_evals_hill_valley,int)): + raise ValueError("The number of evaluations in the Hill Valley Test must be a positive integer") + + + + # Get definitions from kwargs + restarts:int = kwargs.pop("restarts",0) + restart_from_best:str = kwargs.pop("restart_from_best","False") + incpopsize:int = kwargs.pop("incpopsize",2) + bipop:bool = kwargs.pop("bipop",False) + callback:Callable = kwargs.pop("callback",None) + noise_kappa_exponent = kwargs.pop("noise_kappa_exponent",None) + eval_initial_x:bool = kwargs.pop("eval_initial_x",True) + + + + # Adjust the bounds setting to be fed into the definition of Hansen's CMA-ES + h_bounds = [self.bounds[:,0].ravel().tolist(), self.bounds[:,1].ravel().tolist()] + + # set the bounds into the options + self.inopts.set("bounds",h_bounds) + + + # Adjust the verbosity + if self.verbose: + self.inopts.set("verbose",1) + + # Check the dimensionality and x0 compatibility + if not self.x0.size == self.dimension: + # Adjust the dimension and warn the user + warnings.warn(f"The dimension of the initial point is different from the problem dimension." \ + "Adjusting the dimension to {self.dimension}") + if self.dimension < self.x0.size: + self.x0 = np.array(self.x0[:self.dimension]) + else: + self.x0 = np.hstack([self.x0,np.zeros(self.dimension-self.x0.size)]) + + # Set the options from the saved configuration + opts = self.inopts + ###-------------------------------------- + ### This is a direct copy from fmin method + ###-------------------------------------- + + if 1 < 3: # try: # pass on KeyboardInterrupt + + fmin_options = locals().copy() # archive original options + del fmin_options['problem'] + del fmin_options['dim'] + del fmin_options['kwargs'] + del fmin_options['bounds'] + del fmin_options['self'] + del fmin_options['shrinkage_factor'] + del fmin_options['intrinsic_dimension'] + del fmin_options['rejection_factor_c'] + + + if callback is None: + callback = [] + elif callable(callback): + callback = [callback] + + # BIPOP-related variables: + runs_with_small = 0 + small_i = [] + large_i = [] + popsize0 = None # to be evaluated after the first iteration + maxiter0 = None # to be evaluated after the first iteration + base_evals = 0 + + irun = 0 + best = ot.BestSolution() + all_stoppings = [] + while True: # restart loop + sigma_factor = 1 + + # Adjust the population according to BIPOP after a restart. + if not bipop: + # BIPOP not in use, simply double the previous population + # on restart. + if irun > 0: + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + + elif irun == 0: + # Initial run is with "normal" population size; it is + # the large population before first doubling, but its + # budget accounting is the same as in case of small + # population. + poptype = 'small' + + elif sum(small_i) < sum(large_i): + # An interweaved run with small population size + poptype = 'small' + if 11 < 3: # not needed when compared to irun - runs_with_small + restarts += 1 # A small restart doesn't count in the total + runs_with_small += 1 # _Before_ it's used in popsize_lastlarge + + sigma_factor = 0.01**np.random.uniform() # Local search + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = np.floor(popsize0 * popsize_multiplier**(np.random.uniform()**2)) + opts['maxiter'] = min(maxiter0, 0.5 * sum(large_i) / opts['popsize']) + # print('small basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + else: + # A run with large population size; the population + # doubling is implicit with incpopsize. + poptype = 'large' + + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + opts['maxiter'] = maxiter0 + # print('large basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + + if irun and eval(str(fmin_options['restart_from_best'])): + utils.print_warning('CAVE: restart_from_best is often not useful', + verbose=opts['verbose']) + es = CMAEvolutionStrategy(best.x, sigma_factor * self.sigma0, opts) + else: + # Perform the sorting function before inserting the initial point + + + if irun==0: + x0 = self.x0.ravel() + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + else: + rng = np.random.default_rng(opts['seed']) + x0 = rng.uniform(0,1,self.dimension).ravel() + + for idx_,elem in enumerate(x0): + x0[idx_] = self.bounds[idx_,0] + elem*(self.bounds[idx_,1]-self.bounds[idx_,0]) + + del idx_,elem + # Start up the CMA-ES Evolution Strategy + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + + # return opts, es + if callable(problem) and ( + eval_initial_x + or es.opts['CMA_elitist'] == 'initial' + or (es.opts['CMA_elitist'] and + eval_initial_x is None)): + + + x = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + es.f0 = problem(x) + es.best.update([x], es.sent_solutions, + [es.f0], 1) + es.countevals += 1 + es.objective_function = problem # only for the record + + opts = es.opts # processed options, unambiguous + # a hack: + fmin_opts = CMAOptions("unchecked", **fmin_options.copy()) + for k in fmin_opts: + # locals() cannot be modified directly, exec won't work + # in 3.x, therefore + fmin_opts.eval(k, loc={'N': es.N, + 'popsize': opts['popsize']}, + correct_key=False) + + es.logger.append = opts['verb_append'] or es.countiter > 0 or irun > 0 + # es.logger is "the same" logger, because the "identity" + # is only determined by the `verb_filenameprefix` option + logger = es.logger # shortcut + try: + logger.persistent_communication_dict.update( + {'variable_annotations': + problem.variable_annotations}) + except AttributeError: + pass + + + # Set noise handling + #noisehandler = ot.NoiseHandler(es.N, 0) # switched off + #noise_handling = False + #es.noise_handler = noisehandler + + + while not es.stop(): # iteration loop + # X, fit = eval_in_parallel(lambda: es.ask(1)[0], es.popsize, args, repetitions=noisehandler.evaluations-1) + # X, fit = es.ask_and_eval(problem, + # args=[], gradf=None, + # evaluations=noisehandler.evaluations, + # aggregation=np.median, + # parallel_mode=False) # treats NaN with resampling if not parallel_mode + # TODO: check args and in case use args=(noisehandler.evaluations, ) + + + # Start the X array (as an empty list) + X:list = [] + # Iteration counter + n_sampling:int = 0 + + # ask values + n_ask_vals:int = es.popsize + + # ASK Step (Perform loops until reaching actual pupulation size) + while len(X) < es.popsize: + # Get the sample from the CMA-ES + x_samples = es.ask(number=n_ask_vals) + + # Check if each of the sampled points is rejected + for x_samp in x_samples: + if self._reject_point(es,x_samp,shrinkage_factor,n_sampling,rejection_factor_c,irun,intrinsic_dimension): + # Add the rejected point + n_sampling += 1 + else: + # Append the sample to the list + X.append(x_samp) + + # Update the number of asked values + n_ask_vals = es.popsize - len(X) + + + # FIT + fit = [problem(x_i) for x_i in X] + + + # if es.opts['verbose'] > 4: # may be undesirable with dynamic fitness (e.g. Augmented Lagrangian) + # if es.countiter < 2 or min(fit) <= es.best.last.f: + # degrading_iterations_count = 0 # comes first to avoid code check complaint + # else: # min(fit) > es.best.last.f: + # degrading_iterations_count += 1 + # if degrading_iterations_count > 4: + # utils.print_message('%d f-degrading iterations (set verbose<=4 to suppress)' + # % degrading_iterations_count, + # iteration=es.countiter) + + + + es.tell(X, fit, check_points=None) # prepare for next iteration + + for f in callback: + f is None or f(es) + es.disp() + logger.add(# more_data=[noisehandler.evaluations, 10**noisehandler.noiseS] if noise_handling else [], + modulo=1 if es.stop() and logger.modulo else None) + # if (opts['verb_log'] and opts['verb_plot'] and + # (es.countiter % max(opts['verb_plot'], opts['verb_log']) == 0 or es.stop())): + # logger.plot(324) + + + + + # end while not es.stop + if opts['eval_final_mean'] and callable(problem): + mean_pheno = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + fmean = problem(mean_pheno) + es.countevals += 1 + es.best.update([mean_pheno], es.sent_solutions, [fmean], es.countevals) + + best.update(es.best, es.sent_solutions) # in restarted case + # es.best.update(best) + + this_evals = es.countevals - base_evals + base_evals = es.countevals + + # Modify the tabu points archive + if self.num_tabu_points() == 0: + # Modify the tabu points archive + self._check_tabu_points_archive(X[-1],fit[-1],problem,intrinsic_dimension,n_evals_hill_valley,True) + else: + # Add evaluations and modify the Tabu Archive (given that the number of evaluations is not reached) + if not es.countevals >= self.budget: + es.countevals += self._check_tabu_points_archive_2(X[-1],fit[-1],problem,intrinsic_dimension,n_evals_hill_valley, False) + else: + es.countevals += self._check_tabu_points_archive_2(X[-1],fit[-1],problem,intrinsic_dimension,n_evals_hill_valley, False) + + + # BIPOP stats update + + if irun == 0: + popsize0 = opts['popsize'] + maxiter0 = opts['maxiter'] + # XXX: This might be a bug? Reproduced from Matlab + # small_i.append(this_evals) + + if bipop: + if poptype == 'small': + small_i.append(this_evals) + else: # poptype == 'large' + large_i.append(this_evals) + + # final message + if opts['verb_disp']: + es.result_pretty(irun, time.asctime(time.localtime()), + best.f) + + irun += 1 + # if irun > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + # if irun > restarts or 'ftarget' in es.stop() \ + all_stoppings.append(dict(es.stop(check=False))) # keeping the order + if irun - runs_with_small > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + or 'maxfevals' in es.stop(check=False) or 'callback' in es.stop(check=False): + break + opts['verb_append'] = es.countevals + opts['popsize'] = fmin_opts['incpopsize'] * es.sp.popsize # TODO: use rather options? + try: + opts['seed'] += 1 + except TypeError: + pass + + # while irun + + # es.out['best'] = best # TODO: this is a rather suboptimal type for inspection in the shell + if irun: + es.best.update(best) + # TODO: there should be a better way to communicate the overall best + + # Assign the number of iterations and number of function evaluations to the class + self.iterations = es.countiter + self.number_of_function_evaluations = es.countevals + return es.result + (es.stop(), es, logger) + ### 4560 + # TODO refine output, can #args be flexible? + # is this well usable as it is now? + else: # except KeyboardInterrupt: # Exception as e: + if eval(safe_str(opts['verb_disp'])) > 0: + print(' in/outcomment ``raise`` in last line of cma.fmin to prevent/restore KeyboardInterrupt exception') + raise KeyboardInterrupt # cave: swallowing this exception can silently mess up experiments, if ctrl-C is hit + + + + # Call the fmin2 function directly + # res = fmin( + # objective_function=problem, + # x0=self.x0, + # sigma0=self.sigma0, + # options = self.inopts, + # eval_initial_x=True, + # restart_from_best=restart_from_best, + # restarts=restarts, + # incpopsize=incpopsize, + # bipop=bipop, + # callback=callback, + # noise_kappa_exponent=noise_kappa_exponent + # ) + + # Set the best value + #self.number_of_function_evaluations = res[3] + #self.iterations = res[4] + + def reset(self): + # Call the reset method from superclass + super().reset() + + # Restart the Tabu Triplets + self.tabu_triplets = [] + + @property + def tabu_triplets(self)->List[List[Union[np.ndarray,float,int]]]: + return self.__tabu_triplets + + def num_tabu_points(self)->int: + r""" + Returns the number of tabu points. + """ + return len(self.__tabu_triplets) + + @staticmethod + def compute_rejection_radius(dim:int, V:float)->float: + r""" + This is a static method to compute the rejection radius for the tabu points. + + Args: + ------------ + - dim: `int`: The dimension of the problem + - V: `float`: The search space coverage given the tabu points. + """ + return (V**(1.0/dim)/np.sqrt(np.pi))*(gamma_func(dim/2.0+1.0)**(1/dim)) + + @staticmethod + def compute_search_space_coverage(n_rep:int, vol:float, c:float, sigma_0:float, R:int)->float: + r""" + This is a static method to compute the search space coverage given the tabu points. + + Args: + ------------- + - n_rep: `int`: The number of repetitions of the Tabu point in the archive. + - vol: `float`: The volume of the search space. + - c: `float`: The 'coverage factor' as mentioned by De Nobel et al. + - sigma_0: `float`: The initial step size of the CMA-ES algorithm. + - R: `int`: The number of restarts (so far) + """ + + return (n_rep*vol)/(c*sigma_0*R) + + def _reject_point(self, + evol_strategy:CMAEvolutionStrategy, + x_sample:np.ndarray, + shrinkage_factor:float, + n_repetitions:int, + c:float, + R:int, + intrinsic_dimension:Union[None,int])->bool: + + r""" + This function rejects a point if it is within the "tabu zone". + + Args: + ------------- + - evol_strategy: `CMAEvolutionStrategy`: The evolution strategy object (from Nikolaus Hansen's CMA-ES library) + - x_sample: `np.ndarray`: The sample to be evaluated. + - shrinkage_factor: `float`: The shrinkage factor to be applied. + - n_repetitions: `int`: The number of repetitions or resamples so far in the loop. + - c: `float`: The coverage factor as mentioned by de Nobel et al. + - R: `int`: The number of restarts so far. + - intrinsic_dimension: `Union[None,int]`: The intrinsic dimension of the problem. + + Returns: + ------------ + `bool`: A boolean indicating if the point is rejected or not. + NOTE: True means the point is rejected. + """ + + # Initialize a response + resp = False + # Compute the volume of the search space + vol_search = self.compute_space_volume() + + if self.num_tabu_points() == 0: + return False + + # Loop all over the points of the archive + for triplet in self.tabu_triplets: + + # Check if the intrinsic dimension is None (Case 1) + if intrinsic_dimension is None: + # Compute the Mahalanobis distance between the sample and the triplet + diff = x_sample - triplet[0] + dist = evol_strategy.mahalanobis_norm(evol_strategy.gp.geno(diff)) + # Compute the the search space coverage + V_t = self.compute_search_space_coverage(triplet[2], vol_search, c,self.sigma0, R) + # Compute the rejection radius + delta_t = self.compute_rejection_radius(self.dimension, V_t) + # Check if the distance is less than the scaled rejection radius + if dist < shrinkage_factor**n_repetitions*delta_t: + resp = True + break + # Check if the intrinsic dimension is not None (thus iterating the different permutations) + else: + # Resize the triplet in accordance to the intrinsic dimension + triplet_resize:np.ndarray = triplet[0].reshape(-1,intrinsic_dimension,order='F') + # Compute the number of groups + n_groups = triplet_resize.shape[0] + # Iterate over the intrinsic dimension + perms = permutations(range(n_groups),n_groups) + + was_rejected:bool = False + + # Loop all over the possible permutations + for idx_,perm in enumerate(perms): + reshaped_triplet = triplet_resize[perm,:].ravel() + + # Compute the Mahalanobis distance between the sample and the triplet + diff = x_sample - reshaped_triplet + dist = evol_strategy.mahalanobis_norm(evol_strategy.gp.geno(diff)) + # Compute the the search space coverage + V_t = self.compute_search_space_coverage(triplet[2], vol_search, c,self.sigma0, R) + # Compute the rejection radius + delta_t = self.compute_rejection_radius(self.dimension, V_t) + # Check if the distance is less than the scaled rejection radius + if dist < shrinkage_factor**n_repetitions*delta_t: + was_rejected = True + break + + if was_rejected: + resp = True + + + return resp + + def _check_tabu_points_archive(self, + last_x_sample:np.ndarray, + last_f_sample:float, + problem:Union[RealSingleObjective,Callable], + intrinsic_dimension:Optional[int]=None, + n_evals_hill_valley:Optional[int]=10, + just_append:Optional[bool]=False, + )->None: + r""" + This function is used to check the tabu points archive and update it accordingly given the last sample before the new restart. + + Args: + ------------ + - last_x_sample: `np.ndarray`: The last sample evaluated before the restart. + - last_f_sample: `float`: The last function evaluation before the restart. + - problem: `Union[RealSingleObjective,Callable]`: The problem to be evaluated with a `__call__` method implemented evaluating the function. + - intrinsic_dimension: `Optional[int]`: The intrinsic dimensionality of the problem. + - n_evals_hill_valley: `Optional[int]`: The number of evaluations to perform in the Hill Valley Test. + - just_append: `Optional[bool]`: A boolean indicating if the point should be appended to the archive without checking. + """ + + # Case when the archive is empty + if self.num_tabu_points() == 0 or just_append: + self.tabu_triplets.append([last_x_sample,last_f_sample,1]) + else: + # Check if the intrinsic dimension is None (Case 1) + if intrinsic_dimension is None: + # Loop all over the points of the archive + broken_loop = False + for idx,triplet in enumerate(self.tabu_triplets): + # Use the Hill Valley Test to check if the point is in the same basin of attraction + if hill_valley_test(last_x_sample,last_f_sample,triplet[0],triplet[1],problem,n_evals_hill_valley): + self.tabu_triplets[idx][2] += 1 + broken_loop = True + break + + + if not broken_loop: + self.tabu_triplets.append([last_x_sample,last_f_sample,1]) + + + # Check if the intrinsic dimension is not None (thus iterating the different permutations) + else: + # Loop all over the points of the archive + for idx,triplet in enumerate(self.tabu_triplets): + broken_loop = False + # Resize the triplet in accordance to the intrinsic dimension + triplet_resize:np.ndarray = triplet[0].reshape(-1,intrinsic_dimension,order='F') + # Compute the number of groups + n_groups = triplet_resize.shape[0] + # Iterate over the intrinsic dimension + perms = [*permutations(range(n_groups),n_groups)] + + # Initialise a distance array to check the closest invariant point from the actual sample + dist_array:list = [] + + # Loop all over the possible permutations + for _,perm in enumerate(perms): + reshaped_triplet = triplet_resize[perm,:].ravel() + + # Compute the distance between the reshaped triplet and the last sample (euclidean distance) + actual_dist = np.linalg.norm(reshaped_triplet-np.asarray(last_x_sample,dtype=float),ord=2) + + # Append the to the distance array + dist_array.append(actual_dist) + + # Get the index of the closest distance + idx_ = np.argmin(np.asarray(dist_array)) + + compared_perm = perms[idx_] + + # Use the Hill Valley Test to check if the point is in the same basin of attraction + if hill_valley_test(last_x_sample,last_f_sample,triplet_resize[compared_perm,:].ravel(),triplet[1],problem,n_evals_hill_valley): + self.tabu_triplets[idx][2] += 1 + broken_loop = True + break + + if not broken_loop: + self.tabu_triplets.append([last_x_sample,last_f_sample,1]) + + def _check_tabu_points_archive_2(self, + last_x_sample:np.ndarray, + last_f_sample:float, + problem:Union[RealSingleObjective,Callable], + intrinsic_dimension:Optional[int]=None, + n_evals_hill_valley:Optional[int]=10, + just_append:Optional[bool]=False, + )->int: + r""" + This function is used to check the tabu points archive and update it accordingly given the last sample before the new restart. + + Contrary to the original implementation, this function returns the number of function evaluations performed in the Hill Valley Test. + + Args: + ------------ + - last_x_sample: `np.ndarray`: The last sample evaluated before the restart. + - last_f_sample: `float`: The last function evaluation before the restart. + - problem: `Union[RealSingleObjective,Callable]`: The problem to be evaluated with a `__call__` method implemented evaluating the function. + - intrinsic_dimension: `Optional[int]`: The intrinsic dimensionality of the problem. + - n_evals_hill_valley: `Optional[int]`: The number of evaluations to perform in the Hill Valley Test. + - just_append: `Optional[bool]`: A boolean indicating if the point should be appended to the archive without checking. + + Returns: + ------------ + `int`: The number of function evaluations performed in the Hill Valley Test. + """ + + # The following variable is used for counting a number of function evaluations + iter_counter:int = 0 + + # Case when the archive is empty + if self.num_tabu_points() == 0 or just_append: + # Append the first triplet + self.tabu_triplets.append([last_x_sample,last_f_sample,1]) + + # Return 0 automatically since there is no need to compute iterations + return iter_counter + else: + # Check if the intrinsic dimension is None (Case 1) + if intrinsic_dimension is None: + # Loop all over the points of the archive + broken_loop = False + for idx,triplet in enumerate(self.tabu_triplets): + # Use the Hill Valley Test to check if the point is in the same basin of attraction + broken_loop, new_iters = hill_valley_test_2(last_x_sample,last_f_sample,triplet[0],triplet[1],problem,n_evals_hill_valley) + if broken_loop: + self.tabu_triplets[idx][2] += 1 + iter_counter += new_iters + break + + + if not broken_loop: + self.tabu_triplets.append([last_x_sample,last_f_sample,1]) + + + # Return the number of iterations + return iter_counter + + + # Check if the intrinsic dimension is not None (thus iterating the different permutations) + else: + # Loop all over the points of the archive + for idx,triplet in enumerate(self.tabu_triplets): + broken_loop = False + # Resize the triplet in accordance to the intrinsic dimension + triplet_resize:np.ndarray = triplet[0].reshape(-1,intrinsic_dimension,order='F') + # Compute the number of groups + n_groups = triplet_resize.shape[0] + # Iterate over the intrinsic dimension + perms = [*permutations(range(n_groups),n_groups)] + + # Initialise a distance array to check the closest invariant point from the actual sample + dist_array:list = [] + + # Loop all over the possible permutations + for _,perm in enumerate(perms): + reshaped_triplet = triplet_resize[perm,:].ravel() + + # Compute the distance between the reshaped triplet and the last sample (euclidean distance) + actual_dist = np.linalg.norm(reshaped_triplet-np.asarray(last_x_sample,dtype=float),ord=2) + + # Append the to the distance array + dist_array.append(actual_dist) + + # Get the index of the closest distance + idx_ = np.argmin(np.asarray(dist_array)) + + compared_perm = perms[idx_] + + # Use the Hill Valley Test to check if the point is in the same basin of attraction + broken_loop, new_iters = hill_valley_test_2(last_x_sample,last_f_sample,triplet_resize[compared_perm,:].ravel() + ,triplet[1],problem,n_evals_hill_valley) + if broken_loop: + self.tabu_triplets[idx][2] += 1 + iter_counter += new_iters + break + + + if not broken_loop: + self.tabu_triplets.append([last_x_sample,last_f_sample,1]) + + # Return the number of iterations + return iter_counter + + + + + + + + + + + + + \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Rejection_Sampling_CMA_ES.py b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Rejection_Sampling_CMA_ES.py new file mode 100644 index 0000000..02f7930 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Rejection_Sampling_CMA_ES.py @@ -0,0 +1,457 @@ +r""" + This module stores the different wrappers for CMA-ES. + This is an extension of Nikolaus Hansen's et. al. library to perform CMA-ES + and work within this framework +""" + +### -------------------------------------- +### MODULE PROPERTIES +### -------------------------------------- + +__author__ = ["Iván Olarte Rodríguez"] + + +import numpy as np # Import Numpy +from .Pure_CMA_ES import Pure_CMA_ES +import time +from cma.utilities import utils +import cma +from cma import cma_default_options_ +from cma import CMAOptions, CMAEvolutionStrategy +from cma.options_parameters import safe_str +from cma import optimization_tools as ot +from cma import CMADataLogger +from typing import Union, Callable, List, Optional +import warnings + + +class RejectionSamplingCMA_ES(Pure_CMA_ES): + + def __init__(self, + x0:Union[np.ndarray,Callable], + sigma0:float, + budget:int, + inopts:Optional[Union[CMAOptions,dict]]=None, + random_seed:Optional[int]=42, + **kwargs): + + r""" This is the extended constructor for the pure CMA_ES. + + Args: + ----------- + - x0: An initial point to start with CMA-ES + - sigma0: The initial step size + - budget: Number of maximum number of evaluations performed to the problem + - inopts: The initial options required for CMA-ES + - **kwargs: keyword arguments to pass to superclasses + """ + + # Use the superclass from Pure CMA-ES + super().__init__(x0=x0, + sigma0=sigma0, + budget=budget, + inopts=inopts, + random_seed=random_seed, + **kwargs) + + + def __str__(self): + return "Special Injection CMA-ES Optimizer" + + + def __call__(self, + problem:Callable, + dim:Optional[int]=-1, + bounds:Optional[np.ndarray]=None, + sorting_function:Optional[Callable]=None, + **kwargs)->None: + + r""" + This is an overload of the call function to perform the CMA-ES optimization. + + Args: + ----------- + - problem: The problem to be optimized + - dim: The dimension of the problem + - bounds: The bounds of the problem + - sorting_function: The sorting function to be used to sort the samples and shift the permutations. + - **kwargs: Keyword arguments to pass to the CMA-ES optimizer + """ + + # If the sorting function is not given, then, call the superclass + if sorting_function is None: + super().__call__(problem, dim, bounds, **kwargs) + + elif callable(sorting_function): + # This is to handle if the sorting function complies with the callable type + # Call the `Pure_CMA_ES` superclass to check on the problem type and + # set the dimension and bounds + super(Pure_CMA_ES,self).__call__(problem, dim, bounds, **kwargs) + + # Get definitions from kwargs + restarts:int = kwargs.pop("restarts",0) + restart_from_best:str = kwargs.pop("restart_from_best","False") + incpopsize:int = kwargs.pop("incpopsize",2) + bipop:bool = kwargs.pop("bipop",False) + callback:Callable = kwargs.pop("callback",None) + noise_kappa_exponent = kwargs.pop("noise_kappa_exponent",None) + eval_initial_x:bool = kwargs.pop("eval_initial_x",True) + max_loop:int = kwargs.pop("max_loop",100) + + + # Adjust the bounds setting to be fed into the definition of Hansen's CMA-ES + h_bounds = [self.bounds[:,0].ravel().tolist(), self.bounds[:,1].ravel().tolist()] + + # set the bounds into the options + self.inopts.set("bounds",h_bounds) + + + # Adjust the verbosity + if self.verbose: + self.inopts.set("verbose",1) + + # Check the dimensionality and x0 compatibility + if not self.x0.size == self.dimension: + # Adjust the dimension and warn the user + warnings.warn(f"The dimension of the initial point is different from the problem dimension." \ + "Adjusting the dimension to {self.dimension}") + if self.dimension < self.x0.size: + self.x0 = np.array(self.x0[:self.dimension]) + else: + self.x0 = np.hstack([self.x0,np.zeros(self.dimension-self.x0.size)]) + + # Set the options from the saved configuration + opts = self.inopts + + ###-------------------------------------- + ### This is a direct copy from fmin method + ###-------------------------------------- + + if 1 < 3: # try: # pass on KeyboardInterrupt + + fmin_options = locals().copy() # archive original options + del fmin_options['problem'] + del fmin_options['dim'] + del fmin_options['kwargs'] + del fmin_options['bounds'] + del fmin_options['self'] + del fmin_options['sorting_function'] + + if callback is None: + callback = [] + elif callable(callback): + callback = [callback] + + # BIPOP-related variables: + runs_with_small = 0 + small_i = [] + large_i = [] + popsize0 = None # to be evaluated after the first iteration + maxiter0 = None # to be evaluated after the first iteration + base_evals = 0 + + irun = 0 + best = ot.BestSolution() + all_stoppings = [] + while True: # restart loop + sigma_factor = 1 + + # Adjust the population according to BIPOP after a restart. + if not bipop: + # BIPOP not in use, simply double the previous population + # on restart. + if irun > 0: + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + + elif irun == 0: + # Initial run is with "normal" population size; it is + # the large population before first doubling, but its + # budget accounting is the same as in case of small + # population. + poptype = 'small' + + elif sum(small_i) < sum(large_i): + # An interweaved run with small population size + poptype = 'small' + if 11 < 3: # not needed when compared to irun - runs_with_small + restarts += 1 # A small restart doesn't count in the total + runs_with_small += 1 # _Before_ it's used in popsize_lastlarge + + sigma_factor = 0.01**np.random.uniform() # Local search + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = np.floor(popsize0 * popsize_multiplier**(np.random.uniform()**2)) + opts['maxiter'] = min(maxiter0, 0.5 * sum(large_i) / opts['popsize']) + # print('small basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + else: + # A run with large population size; the population + # doubling is implicit with incpopsize. + poptype = 'large' + + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + opts['maxiter'] = maxiter0 + # print('large basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + + if irun and eval(str(fmin_options['restart_from_best'])): + utils.print_warning('CAVE: restart_from_best is often not useful', + verbose=opts['verbose']) + es = CMAEvolutionStrategy(best.x, sigma_factor * self.sigma0, opts) + else: + if irun==0: + x0 = self.x0.ravel() + # Sort x0 + x0,_ = sorting_function([x0]) + # Start up the CMA-ES Evolution Strategy + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + else: + rng = np.random.default_rng(opts['seed']) + x0 = rng.uniform(0,1,self.dimension).ravel() + + for idx_,elem in enumerate(x0): + x0[idx_] = self.bounds[idx_,0] + elem*(self.bounds[idx_,1]-self.bounds[idx_,0]) + + # Sort x0 + x0,_ = sorting_function([x0]) + # Start up the CMA-ES Evolution Strategy + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + # return opts, es + if callable(problem) and ( + eval_initial_x + or es.opts['CMA_elitist'] == 'initial' + or (es.opts['CMA_elitist'] and + eval_initial_x is None)): + x = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + es.f0 = problem(x) + es.best.update([x], es.sent_solutions, + [es.f0], 1) + es.countevals += 1 + es.objective_function = problem # only for the record + + opts = es.opts # processed options, unambiguous + # a hack: + fmin_opts = CMAOptions("unchecked", **fmin_options.copy()) + for k in fmin_opts: + # locals() cannot be modified directly, exec won't work + # in 3.x, therefore + fmin_opts.eval(k, loc={'N': es.N, + 'popsize': opts['popsize']}, + correct_key=False) + + es.logger.append = opts['verb_append'] or es.countiter > 0 or irun > 0 + # es.logger is "the same" logger, because the "identity" + # is only determined by the `verb_filenameprefix` option + logger = es.logger # shortcut + try: + logger.persistent_communication_dict.update( + {'variable_annotations': + problem.variable_annotations}) + except AttributeError: + pass + + + # Set noise handling + noisehandler = ot.NoiseHandler(es.N, 0) # switched off + noise_handling = False + es.noise_handler = noisehandler + + + while not es.stop(): # iteration loop + # X, fit = eval_in_parallel(lambda: es.ask(1)[0], es.popsize, args, repetitions=noisehandler.evaluations-1) + # X, fit = es.ask_and_eval(problem, + # args=[], gradf=None, + # evaluations=noisehandler.evaluations, + # aggregation=np.median, + # parallel_mode=False) # treats NaN with resampling if not parallel_mode + # TODO: check args and in case use args=(noisehandler.evaluations, ) + + #X_2 = es.ask() + # Start X as an empty list + X = [] + counter = 0 # The counter for the number of loops is set to avoid infinite loops + n_sampled = es.popsize + while len(X) < es.popsize and counter < max_loop: + # Get the samples + X_2 = es.ask(n_sampled) + # Perform the sorting function + _, is_sorted = sorting_function(X_2) + + # Append the samples + for idx, elem in enumerate(is_sorted): + if elem: + X.append(X_2[idx]) + + # Update the number of samples + n_sampled = es.popsize - len(X) + counter +=1 + + # Append the solutions + if counter==max_loop: + for idx, elem in enumerate(X_2): + if idx < n_sampled: + X.append(elem) + + + del X_2, idx, elem + # Evaluate the samples + fit = [problem(x) for x in X] + + if es.opts['verbose'] > 4: # may be undesirable with dynamic fitness (e.g. Augmented Lagrangian) + if es.countiter < 2 or min(fit) <= es.best.last.f: + degrading_iterations_count = 0 # comes first to avoid code check complaint + else: # min(fit) > es.best.last.f: + degrading_iterations_count += 1 + if degrading_iterations_count > 4: + utils.print_message('%d f-degrading iterations (set verbose<=4 to suppress)' + % degrading_iterations_count, + iteration=es.countiter) + + + + # Contrary to the Special Injection, the samples are sorted and part + # of the distribution + es.tell(X, fit, check_points=None) # prepare for next iteration + + for f in callback: + f is None or f(es) + es.disp() + logger.add(# more_data=[noisehandler.evaluations, 10**noisehandler.noiseS] if noise_handling else [], + modulo=1 if es.stop() and logger.modulo else None) + if (opts['verb_log'] and opts['verb_plot'] and + (es.countiter % max(opts['verb_plot'], opts['verb_log']) == 0 or es.stop())): + logger.plot(324) + + # end while not es.stop + if opts['eval_final_mean'] and callable(problem): + mean_pheno = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + fmean = problem(mean_pheno) + es.countevals += 1 + es.best.update([mean_pheno], es.sent_solutions, [fmean], es.countevals) + + best.update(es.best, es.sent_solutions) # in restarted case + # es.best.update(best) + + this_evals = es.countevals - base_evals + base_evals = es.countevals + + # BIPOP stats update + + if irun == 0: + popsize0 = opts['popsize'] + maxiter0 = opts['maxiter'] + # XXX: This might be a bug? Reproduced from Matlab + # small_i.append(this_evals) + + if bipop: + if poptype == 'small': + small_i.append(this_evals) + else: # poptype == 'large' + large_i.append(this_evals) + + # final message + if opts['verb_disp']: + es.result_pretty(irun, time.asctime(time.localtime()), + best.f) + + irun += 1 + # if irun > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + # if irun > restarts or 'ftarget' in es.stop() \ + all_stoppings.append(dict(es.stop(check=False))) # keeping the order + if irun - runs_with_small > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + or 'maxfevals' in es.stop(check=False) or 'callback' in es.stop(check=False): + break + opts['verb_append'] = es.countevals + opts['popsize'] = fmin_opts['incpopsize'] * es.sp.popsize # TODO: use rather options? + try: + opts['seed'] += 1 + except TypeError: + pass + + # while irun + + # es.out['best'] = best # TODO: this is a rather suboptimal type for inspection in the shell + if irun: + es.best.update(best) + # TODO: there should be a better way to communicate the overall best + + # Assign the number of iterations and number of function evaluations to the class + self.iterations = es.countiter + self.number_of_function_evaluations = es.countevals + return es.result + (es.stop(), es, logger) + ### 4560 + # TODO refine output, can #args be flexible? + # is this well usable as it is now? + else: # except KeyboardInterrupt: # Exception as e: + if eval(safe_str(opts['verb_disp'])) > 0: + print(' in/outcomment ``raise`` in last line of cma.fmin to prevent/restore KeyboardInterrupt exception') + raise KeyboardInterrupt # cave: swallowing this exception can silently mess up experiments, if ctrl-C is hit + + + + # # Call the fmin2 function directly\ + # res = cma.fmin( + # objective_function=problem, + # x0=self.x0, + # sigma0=self.sigma0, + # options = self.inopts, + # eval_initial_x=True, + # restart_from_best=restart_from_best, + # restarts=restarts, + # incpopsize=incpopsize, + # bipop=bipop, + # callback=callback + # ) + + # # Set the best value + # self.number_of_function_evaluations = res[3] + # self.iterations = res[4] + + else: + raise AttributeError("The sorting function should be a callable object", + name="sorting function", + obj=sorting_function) + + @property + def inopts(self)->CMAOptions: + r""" + This is an overload of the getter for the inopts property + """ + return self.__inopts + + @inopts.setter + def inopts(self, new_inopts:Union[CMAOptions,dict]): + r""" + This is an overload of the setter for the inopts property + """ + + if new_inopts is None or (isinstance(new_inopts,list) and len(new_inopts)==0 ): + # Start the object with + self.__inopts = CMAOptions("unchecked",new_inopts).complement() + + elif isinstance(new_inopts,dict): + self.__inopts = CMAOptions("unchecked",**new_inopts.copy()).complement() + + elif isinstance(new_inopts,CMAOptions): + self.__inopts = new_inopts.complement() + + else: + raise AttributeError("The passed update is not a dictionary, empty list or ``CMAOptions` ") + + # Set the maximum number of iterations + self.__inopts.set("maxfevals",self.budget) + + # Set the random seed + self.__inopts.set("seed",self.random_seed) + + # Do not set to evaluate the final mean + self.__inopts.set("eval_final_mean",False) + + + + diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Special_Injection_CMA_ES.py b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Special_Injection_CMA_ES.py new file mode 100644 index 0000000..d7cef77 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/Special_Injection_CMA_ES.py @@ -0,0 +1,447 @@ +r""" + This module stores the different wrappers for CMA-ES. + This is an extension of Nikolaus Hansen's et. al. library to perform CMA-ES + and work within this framework +""" + +### -------------------------------------- +### MODULE PROPERTIES +### -------------------------------------- + +__author__ = ["Iván Olarte Rodríguez"] + + +import numpy as np # Import Numpy +from .Pure_CMA_ES import Pure_CMA_ES +from ..AbstractAlgorithm import AbstractAlgorithm +import time +from cma.utilities import utils +from cma import cma_default_options_ +from cma import CMAOptions, CMAEvolutionStrategy +from cma.options_parameters import safe_str +from cma import optimization_tools as ot +from cma import CMADataLogger +from typing import Union, Callable, List, Optional +import warnings + + +class SpecialInjectionCMA_ES(Pure_CMA_ES): + + def __init__(self, + x0:Union[np.ndarray,Callable], + sigma0:float, + budget:int, + inopts:Optional[Union[CMAOptions,dict]]=None, + random_seed:Optional[int]=42, + **kwargs): + + r""" This is the extended constructor for the pure CMA_ES. + + Args: + ----------- + - x0: An initial point to start with CMA-ES + - sigma0: The initial step size + - budget: Number of maximum number of evaluations performed to the problem + - inopts: The initial options required for CMA-ES + - **kwargs: keyword arguments to pass to superclasses + """ + + # Use the superclass from Pure CMA-ES + super().__init__(x0=x0, + sigma0=sigma0, + budget=budget, + inopts=inopts, + random_seed=random_seed, + **kwargs) + + # Correct the options + self._correct_inopts() + + def _correct_inopts(self): + r""" + This function corrects the options for the CMA-ES + """ + + # Since there is a need for injection, then, the Active CMA-ES should be deactivated. + self.__inopts.set("CMA_active",False) + + + def __str__(self): + return "Special Injection CMA-ES Optimizer" + + + def __call__(self, + problem, + dim:Optional[int]=-1, + bounds:Optional[np.ndarray]=None, + sorting_function:Optional[Callable]=None, + **kwargs)->None: + + r""" + This is an overload of the call function to perform the CMA-ES optimization. + + Args: + ----------- + - problem: The problem to be optimized + - dim: The dimension of the problem + - bounds: The bounds of the problem + - sorting_function: The sorting function to be used to sort the samples and shift the permutations. + - **kwargs: Keyword arguments to pass to the CMA-ES optimizer + """ + + # If the sorting function is not given, then, call the superclass + if sorting_function is None: + super().__call__(problem, dim, bounds, **kwargs) + + elif callable(sorting_function): + # This is to handle if the sorting function complies with the callable type + # Call the `Pure_CMA_ES` superclass to check on the problem type and + # set the dimension and bounds + super(Pure_CMA_ES,self).__call__(problem, dim, bounds, **kwargs) + + # Get definitions from kwargs + restarts:int = kwargs.pop("restarts",0) + restart_from_best:str = kwargs.pop("restart_from_best","False") + incpopsize:int = kwargs.pop("incpopsize",2) + bipop:bool = kwargs.pop("bipop",False) + callback:Callable = kwargs.pop("callback",None) + noise_kappa_exponent = kwargs.pop("noise_kappa_exponent",None) + eval_initial_x:bool = kwargs.pop("eval_initial_x",True) + + + # Adjust the bounds setting to be fed into the definition of Hansen's CMA-ES + h_bounds = [self.bounds[:,0].ravel().tolist(), self.bounds[:,1].ravel().tolist()] + + # set the bounds into the options + self.inopts.set("bounds",h_bounds) + + + # Adjust the verbosity + if self.verbose: + self.inopts.set("verbose",1) + + # Check the dimensionality and x0 compatibility + if not self.x0.size == self.dimension: + # Adjust the dimension and warn the user + warnings.warn(f"The dimension of the initial point is different from the problem dimension." \ + "Adjusting the dimension to {self.dimension}") + if self.dimension < self.x0.size: + self.x0 = np.array(self.x0[:self.dimension]) + else: + self.x0 = np.hstack([self.x0,np.zeros(self.dimension-self.x0.size)]) + + # Set the options from the saved configuration + opts = self.inopts + + ###-------------------------------------- + ### This is a direct copy from fmin method + ###-------------------------------------- + + if 1 < 3: # try: # pass on KeyboardInterrupt + + fmin_options = locals().copy() # archive original options + del fmin_options['problem'] + del fmin_options['dim'] + del fmin_options['kwargs'] + del fmin_options['bounds'] + del fmin_options['self'] + del fmin_options['sorting_function'] + + + if callback is None: + callback = [] + elif callable(callback): + callback = [callback] + + # BIPOP-related variables: + runs_with_small = 0 + small_i = [] + large_i = [] + popsize0 = None # to be evaluated after the first iteration + maxiter0 = None # to be evaluated after the first iteration + base_evals = 0 + + irun = 0 + best = ot.BestSolution() + all_stoppings = [] + while True: # restart loop + sigma_factor = 1 + + # Adjust the population according to BIPOP after a restart. + if not bipop: + # BIPOP not in use, simply double the previous population + # on restart. + if irun > 0: + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + + elif irun == 0: + # Initial run is with "normal" population size; it is + # the large population before first doubling, but its + # budget accounting is the same as in case of small + # population. + poptype = 'small' + + elif sum(small_i) < sum(large_i): + # An interweaved run with small population size + poptype = 'small' + if 11 < 3: # not needed when compared to irun - runs_with_small + restarts += 1 # A small restart doesn't count in the total + runs_with_small += 1 # _Before_ it's used in popsize_lastlarge + + sigma_factor = 0.01**np.random.uniform() # Local search + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = np.floor(popsize0 * popsize_multiplier**(np.random.uniform()**2)) + opts['maxiter'] = min(maxiter0, 0.5 * sum(large_i) / opts['popsize']) + # print('small basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + else: + # A run with large population size; the population + # doubling is implicit with incpopsize. + poptype = 'large' + + popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) + opts['popsize'] = popsize0 * popsize_multiplier + opts['maxiter'] = maxiter0 + # print('large basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) + + + if irun and eval(str(fmin_options['restart_from_best'])): + utils.print_warning('CAVE: restart_from_best is often not useful', + verbose=opts['verbose']) + es = CMAEvolutionStrategy(best.x, sigma_factor * self.sigma0, opts) + else: + if irun==0: + x0 = self.x0.ravel() + # Sort x0 + x0,_ = sorting_function([x0]) + # Start up the CMA-ES Evolution Strategy + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + else: + rng = np.random.default_rng(opts['seed']) + x0 = rng.uniform(0,1,self.dimension).ravel() + + for idx_,elem in enumerate(x0): + x0[idx_] = self.bounds[idx_,0] + elem*(self.bounds[idx_,1]-self.bounds[idx_,0]) + + # Sort x0 + x0,_ = sorting_function([x0]) + # Start up the CMA-ES Evolution Strategy + es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + # # Perform the sorting function before inserting the initial point + # + # es = CMAEvolutionStrategy(x0, sigma_factor * self.sigma0, opts) + # return opts, es + if callable(problem) and ( + eval_initial_x + or es.opts['CMA_elitist'] == 'initial' + or (es.opts['CMA_elitist'] and + eval_initial_x is None)): + + + x = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + es.f0 = problem(x) + es.best.update([x], es.sent_solutions, + [es.f0], 1) + es.countevals += 1 + es.objective_function = problem # only for the record + + opts = es.opts # processed options, unambiguous + # a hack: + fmin_opts = CMAOptions("unchecked", **fmin_options.copy()) + for k in fmin_opts: + # locals() cannot be modified directly, exec won't work + # in 3.x, therefore + fmin_opts.eval(k, loc={'N': es.N, + 'popsize': opts['popsize']}, + correct_key=False) + + es.logger.append = opts['verb_append'] or es.countiter > 0 or irun > 0 + # es.logger is "the same" logger, because the "identity" + # is only determined by the `verb_filenameprefix` option + logger = es.logger # shortcut + try: + logger.persistent_communication_dict.update( + {'variable_annotations': + problem.variable_annotations}) + except AttributeError: + pass + + + # Set noise handling + noisehandler = ot.NoiseHandler(es.N, 0) # switched off + noise_handling = False + es.noise_handler = noisehandler + + + while not es.stop(): # iteration loop + # X, fit = eval_in_parallel(lambda: es.ask(1)[0], es.popsize, args, repetitions=noisehandler.evaluations-1) + X, fit = es.ask_and_eval(problem, + args=[], gradf=None, + evaluations=noisehandler.evaluations, + aggregation=np.median, + parallel_mode=False) # treats NaN with resampling if not parallel_mode + # TODO: check args and in case use args=(noisehandler.evaluations, ) + + # if es.opts['verbose'] > 4: # may be undesirable with dynamic fitness (e.g. Augmented Lagrangian) + # if es.countiter < 2 or min(fit) <= es.best.last.f: + # degrading_iterations_count = 0 # comes first to avoid code check complaint + # else: # min(fit) > es.best.last.f: + # degrading_iterations_count += 1 + # if degrading_iterations_count > 4: + # utils.print_message('%d f-degrading iterations (set verbose<=4 to suppress)' + # % degrading_iterations_count, + # iteration=es.countiter) + + # Perform the sorting function + new_X, is_sorted = sorting_function(X) + checkPoints = None + if np.all(np.array(is_sorted,dtype=bool)) == False: + # If the array is not sorted then check the points to clip + checkPoints = np.where(np.array(is_sorted,dtype=bool) == False)[0].ravel().tolist() + + es.tell(new_X, fit, check_points=checkPoints) # prepare for next iteration + for f in callback: + f is None or f(es) + es.disp() + logger.add(# more_data=[noisehandler.evaluations, 10**noisehandler.noiseS] if noise_handling else [], + modulo=1 if es.stop() and logger.modulo else None) + if (opts['verb_log'] and opts['verb_plot'] and + (es.countiter % max(opts['verb_plot'], opts['verb_log']) == 0 or es.stop())): + logger.plot(324) + + # end while not es.stop + if opts['eval_final_mean'] and callable(problem): + mean_pheno = es.gp.pheno(es.mean, + into_bounds=es.boundary_handler.repair, + archive=es.sent_solutions) + fmean = problem(mean_pheno) + es.countevals += 1 + es.best.update([mean_pheno], es.sent_solutions, [fmean], es.countevals) + + best.update(es.best, es.sent_solutions) # in restarted case + # es.best.update(best) + + this_evals = es.countevals - base_evals + base_evals = es.countevals + + # BIPOP stats update + + if irun == 0: + popsize0 = opts['popsize'] + maxiter0 = opts['maxiter'] + # XXX: This might be a bug? Reproduced from Matlab + # small_i.append(this_evals) + + if bipop: + if poptype == 'small': + small_i.append(this_evals) + else: # poptype == 'large' + large_i.append(this_evals) + + # final message + if opts['verb_disp']: + es.result_pretty(irun, time.asctime(time.localtime()), + best.f) + + irun += 1 + # if irun > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + # if irun > restarts or 'ftarget' in es.stop() \ + all_stoppings.append(dict(es.stop(check=False))) # keeping the order + if irun - runs_with_small > fmin_opts['restarts'] or 'ftarget' in es.stop() \ + or 'maxfevals' in es.stop(check=False) or 'callback' in es.stop(check=False): + break + opts['verb_append'] = es.countevals + opts['popsize'] = fmin_opts['incpopsize'] * es.sp.popsize # TODO: use rather options? + try: + opts['seed'] += 1 + except TypeError: + pass + + # while irun + + # es.out['best'] = best # TODO: this is a rather suboptimal type for inspection in the shell + if irun: + es.best.update(best) + # TODO: there should be a better way to communicate the overall best + + # Assign the number of iterations and number of function evaluations to the class + self.iterations = es.countiter + self.number_of_function_evaluations = es.countevals + return es.result + (es.stop(), es, logger) + ### 4560 + # TODO refine output, can #args be flexible? + # is this well usable as it is now? + else: # except KeyboardInterrupt: # Exception as e: + if eval(safe_str(opts['verb_disp'])) > 0: + print(' in/outcomment ``raise`` in last line of cma.fmin to prevent/restore KeyboardInterrupt exception') + raise KeyboardInterrupt # cave: swallowing this exception can silently mess up experiments, if ctrl-C is hit + + + + # # Call the fmin2 function directly\ + # res = cma.fmin( + # objective_function=problem, + # x0=self.x0, + # sigma0=self.sigma0, + # options = self.inopts, + # eval_initial_x=True, + # restart_from_best=restart_from_best, + # restarts=restarts, + # incpopsize=incpopsize, + # bipop=bipop, + # callback=callback + # ) + + # # Set the best value + # self.number_of_function_evaluations = res[3] + # self.iterations = res[4] + + else: + raise AttributeError("The sorting function should be a callable object", + name="sorting function", + obj=sorting_function) + + @property + def inopts(self)->CMAOptions: + r""" + This is an overload of the getter for the inopts property + """ + return self.__inopts + + @inopts.setter + def inopts(self, new_inopts:Union[CMAOptions,dict]): + r""" + This is an overload of the setter for the inopts property + """ + + if new_inopts is None or (isinstance(new_inopts,list) and len(new_inopts)==0 ): + # Start the object with + self.__inopts = CMAOptions("unchecked",new_inopts).complement() + + elif isinstance(new_inopts,dict): + self.__inopts = CMAOptions("unchecked",**new_inopts.copy()).complement() + + elif isinstance(new_inopts,CMAOptions): + self.__inopts = new_inopts.complement() + + else: + raise AttributeError("The passed update is not a dictionary, empty list or ``CMAOptions` ") + + # Set the maximum number of iterations + self.__inopts.set("maxfevals",self.budget) + + # Set the random seed + self.__inopts.set("seed",self.random_seed) + + # Do not set to evaluate the final mean + self.__inopts.set("eval_final_mean",False) + + # Correct the options + self._correct_inopts() + + + + diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__init__.py b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__init__.py new file mode 100644 index 0000000..de4dafd --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__init__.py @@ -0,0 +1 @@ +#from .utils import hill_valley_test \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Normal_Injection_CMA_ES.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Normal_Injection_CMA_ES.cpython-310.pyc new file mode 100644 index 0000000..09a4e38 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Normal_Injection_CMA_ES.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Pure_CMA_ES.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Pure_CMA_ES.cpython-310.pyc new file mode 100644 index 0000000..cc82f25 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Pure_CMA_ES.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Pure_CMA_ES_Tabu.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Pure_CMA_ES_Tabu.cpython-310.pyc new file mode 100644 index 0000000..f041144 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Pure_CMA_ES_Tabu.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Rejection_Sampling_CMA_ES.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Rejection_Sampling_CMA_ES.cpython-310.pyc new file mode 100644 index 0000000..981317d Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Rejection_Sampling_CMA_ES.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Special_Injection_CMA_ES.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Special_Injection_CMA_ES.cpython-310.pyc new file mode 100644 index 0000000..2e9767c Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/Special_Injection_CMA_ES.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..41db0fe Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__init__.py b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..1cf01f2 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__pycache__/hill_valley_test.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__pycache__/hill_valley_test.cpython-310.pyc new file mode 100644 index 0000000..bbf7884 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/CMA_ES_Wrappers/utils/__pycache__/hill_valley_test.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__init__.py b/mylib/lib_BO_torch_repo/Algorithms/__init__.py new file mode 100644 index 0000000..3d2a297 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/__init__.py @@ -0,0 +1,6 @@ +from .BayesianOptimization.Vanilla_BO.Vanilla_BO import Vanilla_BO +from .BayesianOptimization.Vanilla_BO.Vanilla_BO_Combinatorial import Vanilla_BO_Combinatorial +#from .CMA_ES_Wrappers.Pure_CMA_ES import Pure_CMA_ES +#from .CMA_ES_Wrappers.Pure_CMA_ES_Tabu import Pure_CMA_ES_Tabu +#from .CMA_ES_Wrappers.Special_Injection_CMA_ES import SpecialInjectionCMA_ES +#from .CMA_ES_Wrappers.Rejection_Sampling_CMA_ES import RejectionSamplingCMA_ES \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractAlgorithm.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractAlgorithm.cpython-310.pyc new file mode 100644 index 0000000..2bea054 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractAlgorithm.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractAlgorithm.cpython-311.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractAlgorithm.cpython-311.pyc new file mode 100644 index 0000000..32d21e9 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractAlgorithm.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractBayesianOptimizer.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractBayesianOptimizer.cpython-310.pyc new file mode 100644 index 0000000..d14c66e Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractBayesianOptimizer.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractBayesianOptimizer.cpython-311.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractBayesianOptimizer.cpython-311.pyc new file mode 100644 index 0000000..e6ee19e Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/AbstractBayesianOptimizer.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/CMA_ES_Wrappers.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/CMA_ES_Wrappers.cpython-310.pyc new file mode 100644 index 0000000..83f8f00 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/CMA_ES_Wrappers.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO.cpython-310.pyc new file mode 100644 index 0000000..0cbf035 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO.cpython-311.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO.cpython-311.pyc new file mode 100644 index 0000000..5dd826d Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO_Combinatorial.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO_Combinatorial.cpython-310.pyc new file mode 100644 index 0000000..1d5fa0f Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/Vanilla_BO_Combinatorial.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..8d453f9 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/__pycache__/__init__.cpython-311.pyc b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..bc05629 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/__pycache__/__init__.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/utils/__pycache__/utilities.cpython-310.pyc b/mylib/lib_BO_torch_repo/Algorithms/utils/__pycache__/utilities.cpython-310.pyc new file mode 100644 index 0000000..a03ee68 Binary files /dev/null and b/mylib/lib_BO_torch_repo/Algorithms/utils/__pycache__/utilities.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/Algorithms/utils/utilities.py b/mylib/lib_BO_torch_repo/Algorithms/utils/utilities.py new file mode 100644 index 0000000..32a5bc8 --- /dev/null +++ b/mylib/lib_BO_torch_repo/Algorithms/utils/utilities.py @@ -0,0 +1,192 @@ +r""" +This is a utility module that contains utility functions that are used in the algorithms module. +""" + +import numpy as np +import os +from typing import List, Tuple, Optional, Union, Callable + + +def generate_random_sample(lower_bound:float, upper_bound:float) -> float: + """ + Generate a random sample of size n from a uniform distribution between lower_bound and upper_bound. + + Args: + -------- + lower_bound: `float`: The lower bound of the distribution + upper_bound: `float`: The upper bound of the distribution + + Returns: + -------- + `np.ndarray`: A numpy array of size n with samples from the uniform distribution. + """ + return np.random.uniform(low=lower_bound, high=upper_bound, size=1) + +def generate_random_array(lower_bounds:Union[float,List[float],np.ndarray], + upper_bounds:Union[float,List[float],np.ndarray], + n:int, + shape:Optional[Tuple]) -> np.ndarray: + """ + Generate a random sample of size n from a uniform distribution between lower_bound and upper_bound. + + Args: + -------- + lower_bound: `float`: The lower bound of the distribution + upper_bound: `float`: The upper bound of the distribution + n: `int`: The number of samples to generate. + shape: `Optional[Tuple]`: The shape of the array to be generated. + + Returns: + -------- + `np.ndarray`: A numpy array of size n with samples from the uniform distribution. + """ + # Check if lower_bounds is a scalar + if isinstance(lower_bounds, (float, int)): + lower_bounds = [lower_bounds]*n + + # Check if upper_bounds is a scalar + if isinstance(upper_bounds, (float, int)): + upper_bounds = [upper_bounds]*n + + + return np.random.uniform(low=lower_bounds, high=upper_bounds, size=n) + +def hill_valley_test(x_0:Union[np.ndarray,List[float]], + fit_x_0:float, + x_1:Union[np.ndarray,List[float]], + fit_x_1:float, + func:Callable, + nt:Optional[int]=10, + tolerance:Optional[Union[np.float64,float]]=np.finfo(float).eps)->bool: + r""" + This is a function which performs the Hill Valley Test to + detect if two points correspond to the same Basin of attraction. + + Args: + -------------- + - x_0: `Union[np.ndarray,List[float]]` : The initial point to trace the line + - fit_x_0: `float`: The function evaluation at `x_0` + - x_1: `Union[np.ndarray,List[float]]`: The endpoint to trace the line + - fit_x_1: `float`: The function evaluation at `x_1` + - func: `Callable`: A callable object, with an implemented `__call__(:np.ndarray)->float`, denoting a function evaluation. + - nt: `Optional[int]`: A number of between points to evaluate. + - tolerance: `Optional[Union[np.float64,float]]`: A tolerance to use for the comparison (just to determine the points are very similar). + + Returns: + -------------- + - `bool`: A boolean value, True if the points are in the same basin of attraction, False otherwise. + """ + + # Perform a check-up for the inputs + if isinstance(x_0,list): + # Convert + x_0:np.ndarray = np.array(x_0, + dtype=float, + copy=True, + subok=False).ravel() + + if isinstance(x_1,list): + # Convert + x_1:np.ndarray = np.array(x_1, + dtype=float, + copy=True, + subok=False).ravel() + + + # Compute a difference vector + diff_vect:np.ndarray = np.subtract(x_1,x_0,dtype=np.float64) + # Compute a range of points + r_points = [*range(1,nt+1,1)] + + # This is to check the special case both points are very similar. + if np.linalg.norm(diff_vect) <= tolerance and np.abs(fit_x_0 - fit_x_1) <= tolerance: + return True + + # A placeholder to return the heuristic + ret_var:bool = True + + for k in r_points: + proportion:float = k/(nt+1.0) + + # Compute x_test + x_test:np.ndarray = np.add(x_0,proportion*diff_vect,dtype=np.float64) + + # Compute the function evaluation at x_test + f_test:float = func(x_test) + + if max(fit_x_0,fit_x_1) <= f_test: + ret_var:bool = False + break + + return ret_var + +def hill_valley_test_2(x_0:Union[np.ndarray,List[float]], + fit_x_0:float, + x_1:Union[np.ndarray,List[float]], + fit_x_1:float, + func:Callable, + nt:Optional[int]=10, + tolerance:Optional[Union[np.float64,float]]=np.finfo(float).eps)->Tuple[bool,int]: + r""" + This is the second variant of the function which performs the Hill Valley Test to + detect if two points correspond to the same Basin of attraction. In this case, + instead of just returning if the hill valley test is passed or not, it also returns + the number of function evaluations. + + Args: + -------------- + - x_0: `Union[np.ndarray,List[float]]` : The initial point to trace the line + - fit_x_0: `float`: The function evaluation at `x_0` + - x_1: `Union[np.ndarray,List[float]]`: The endpoint to trace the line + - fit_x_1: `float`: The function evaluation at `x_1` + - func: `Callable`: A callable object, with an implementend `__call__(:np.ndarray)->float`, denoting a function evaluation. + - nt: `Optional[int]`: A number of between points to evaluate. + - tolerance: `Optional[Union[np.float64,float]]`: A tolerance to use for the comparison (just to determine the points are very similar). + + Returns: + -------------- + - `Tuple[bool,int]`: A tuple containing a boolean value and the number of function evaluations. + """ + + # Perform a check-up for the inputs + if isinstance(x_0,list): + # Convert + x_0:np.ndarray = np.array(x_0, + dtype=float, + copy=True, + subok=False).ravel() + + if isinstance(x_1,list): + # Convert + x_1:np.ndarray = np.array(x_1, + dtype=float, + copy=True, + subok=False).ravel() + + + # Compute a difference vector + diff_vect:np.ndarray = np.subtract(x_1,x_0,dtype=np.float64) + # Compute a range of points + r_points = [*range(1,nt+1,1)] + + # This is to check the special case both points are very similar. + if np.linalg.norm(diff_vect) <= tolerance and np.abs(fit_x_0 - fit_x_1) <= tolerance: + return True, 0 + + # A placeholder to return the heuristic + ret_var:bool = True + + for k in r_points: + proportion:float = k/(nt+1.0) + + # Compute x_test + x_test:np.ndarray = np.add(x_0,proportion*diff_vect,dtype=np.float64) + + # Compute the function evaluation at x_test + f_test:float = func(x_test) + + if max(fit_x_0,fit_x_1) <= f_test: + ret_var:bool = False + break + + return ret_var, k diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/2d_postprocessing_plotting.py b/mylib/lib_BO_torch_repo/GlobalUtilities/2d_postprocessing_plotting.py new file mode 100644 index 0000000..259ed17 --- /dev/null +++ b/mylib/lib_BO_torch_repo/GlobalUtilities/2d_postprocessing_plotting.py @@ -0,0 +1,1335 @@ +r""" +This is a module which will be used to generate convergence plots for 2D Modular problems + +--------------------- +NOTE: +This is just a template, which will be further developed in the future for other cases +-------------------- +""" + +__author__ = ["Iván Olarte Rodríguez"] +__institute__ = ["Leiden Institute of Advanced Computer Science"] + + +# Import libraries/modules +import os, sys +from IOH_parser import IOH_Parser # Parser defined within this context +from pathlib import Path +import numpy as np +import pandas as pd +from typing import Union, List, Callable, Iterable, Dict + +#Converters +__idx_space_dict:dict = {1:"bimodal", + 2:"multimodal", + 3:"plateau", + 4:"ackley", + 5:"gramacy_lee", + 6:"levy", + 7:"rastrigin", + 8:"schwefel", + 9:"sum_different_powers", + 10:"forrester", + 11:"sphere", + 12:"alpine", + 13:"styblinski_tank", + 14:"salomon"} + + +### ------------------ +### CONSTANTS +### ------------------ + +# IMMUTABLE +PROBLEMS_MODULES:tuple = ("NonBBOB","NonBBOBWithSubspace") +ALGORITHMS_MODULES:tuple = ("injection","pure","rejection_sampling") +OUTCMAES_FILENAMES:tuple = ("axlen.dat", + "axlencorr.dat", + "axlenprec.dat", + "fit.dat", + "stddev.dat", + "xmean.dat", + "xrecentbest.dat") +INVARIANCE_MODES:tuple = ("average","maximum") + +RELEVANT_OUTCMAES:list = [OUTCMAES_FILENAMES[3],OUTCMAES_FILENAMES[5],OUTCMAES_FILENAMES[6]] +FID_LIMITS:list = [*range(1,15)] + +# TODO: Filled by the user +ROOT_MODULAR_EXPERIMENTS:str = os.path.join(os.getcwd(),"ModularExperiments") +ROOT_TWO_DIMENSIONAL_PROBLEMS_REPO:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/Two_Dimensional" + + +### ------------------- +### VARIOUS FUNCTIONS +### ------------------- + +def _convert_2_path_object(path_:Union[str,Path])->Path: + r""" + This is a helper function to use to convert path_strings to Path + + Args: + -------------------- + - path_: `Union[str,Path]`: A string path to convert + + Returns: + - A ``Path` object + """ + + if isinstance(path_,Path): + return path_ + elif isinstance(path_,str): + return Path(path_) + else: + raise AttributeError("given path not an instance of Path or a string", name="path_",obj=path_) + +def _return_identifiers_from_file_path(path_:Union[str,Path])->Dict: + r""" + This is a function which just identifies all the problem parameters given a path. + + Args: + ------------------- + - path_: `Union[str,Path]`: A path object identifying a 'Run_' instance + + Returns: + ------------------- + A dictionary with the keys; + - func_type:`str`: The function type, which should have two options: ("NonBBOB","NonBBOBWithSubspace") + - inv_mode:`str` = The invariance mode: ('average', 'maximum) + - cur_alg:`str` = The algorithm applied to the problem ('pure','injection','rejection_sampling') + - func_id:`int` = The id alias of the function (so far an integer between 1 to 14) + - func_name:`str` = The actual name of the function (check the namespace `__idx_space_dict`) + - run:`int` = The integer mapping a run number + """ + + # Break down the path to extract information regarding the function + trial_path:Path = _convert_2_path_object(path_) + + # Transform the path to posix ("/") + trial_path_ = trial_path.as_posix() + + # Split all the datastructure + data_struct_full:list = trial_path_.split("/") + data_struct:list = data_struct_full[-5:] + + # Get the function type, invariance mode, function id and run + func_type:str = data_struct[0] + inv_mode:str = data_struct[1] + cur_alg:str = data_struct[2] + func_id:int = int(data_struct[3].replace("f","")) + func_name:str = __idx_space_dict[func_id] + run_:int = int(data_struct[4].replace("Run_","")) + + + return {"func_type":func_type, + "inv_mode":inv_mode, + "cur_alg":cur_alg, + "func_id":func_id, + "func_name":func_name, + "run":run_} + + + +def append_global_iteration_lambda_outcmaes(outcmaes_func:Callable)->Callable: + r""" + This is a function which receives an outcmaes importing function (ones which takes a saved) + cmaes file and converts to a `pandas dataframe` and appends two columns to the dataframe, + namely the number of restarts and the current population size. + """ + + def append_restart_column(init_df:pd.DataFrame)->pd.DataFrame: + # Extract the iteration column + init_df['restart_count'] = (init_df['iteration'] == 1).cumsum()-1 + + return init_df + + def append_lambda_column(init_df:pd.DataFrame)->pd.DataFrame: + init_df['lamda'] = init_df.groupby('restart_count')['evaluation'].diff() + init_df['lamda'] = init_df['lamda'].fillna(init_df.groupby('restart_count')['lamda'].shift(-1)) + + init_df['lamda'] = init_df['lamda'].fillna(init_df['lamda'].shift(1) * 2) + + init_df['lamda'] =init_df['lamda'].astype(int) + return init_df + + def load_function(data_path:Union[str,Path])->pd.DataFrame: + cur_df = outcmaes_func(data_path) + cur_df_1 = append_restart_column(cur_df) + cur_df_2 = append_lambda_column(cur_df_1) + + return cur_df_2 + + return load_function + +@append_global_iteration_lambda_outcmaes +def load_fit_cmaes(path:Union[str,Path])->pd.DataFrame: + r""" + This is just a function which calls the `fit.dat` generated from a CMA-ES run + by using `pycmaes` (The library from Niko Hansen) + """ + + if not path.endswith("fit.dat"): + raise AttributeError("The path does not correspond to the `fit.dat` file", + name="path", + obj=path) + + # These are the tile columns + # This file does not require computing some dimension of the problem + TITLE_COLUMNS:dict = {"iteration":int, + "evaluation":int, + "sigma":float, + "axis_ratio":float, + "bestever":float, + "best":float, + "median":float, + "worst_objective_function_value":float} + + # Generate the `pandas` Dataframe + import warnings + + warnings.simplefilter(action='ignore', category=pd.errors.ParserWarning) + with warnings.catch_warnings(): + df = pd.read_csv(path, + sep=" ", + header=None, + skiprows=[0], + names=TITLE_COLUMNS.keys(), + index_col=False, + dtype=TITLE_COLUMNS, + ) + + return df + +@append_global_iteration_lambda_outcmaes +def load_xmean_cmaes(path:Union[str,Path])->pd.DataFrame: + r""" + This is just a function which calls the `xmean.dat` generated from a CMA-ES run + by using `pycmaes` (The library from Niko Hansen) + """ + + if not path.endswith("xmean.dat"): + raise AttributeError("The path does not correspond to the `xmean.dat` file", + name="path", + obj=path) + + # These are the tile columns + # NOTE:This file does require computing some dimension of the problem + + TITLE_COLUMNS:dict = {"iteration":int, + "evaluation":int, + "void0":int, + "void1":int, + "void2":int, + "xmean":object} + + with open(file=path,mode="r",encoding="utf-8") as f: + _ = f.readline() + second_line = f.readline() + + # Get the number of variables by performing the split + items = second_line.split(" ") + + #close the IOStream + f.close() + + + ncols_in_file:int = len(items) + problem_dimension = ncols_in_file - len(TITLE_COLUMNS) + 1 + + del items, second_line + + # Extract the void columns + void_cols = [] + non_void_idxs = [] + + for idx, title in enumerate(TITLE_COLUMNS.keys()): + if title.startswith("void"): + void_cols.append(title) + else: + non_void_idxs.append(idx) + + for idx in range(6,5+problem_dimension): + non_void_idxs.append(idx) + + # Generate the `pandas` Dataframe + df = pd.read_csv(filepath_or_buffer=path, + sep=" ", + skiprows=0, + #header=0, + #names=TITLE_COLUMNS.keys(), + index_col=False, + usecols=non_void_idxs, + #dtype=TITLE_COLUMNS, + ) + + for a in range(2): + cur_col_name = df.columns[a] + + # Reassign the name + curkey = [*TITLE_COLUMNS][a] + df = df.rename(columns={cur_col_name:curkey}) + + for a in range(2,(2+problem_dimension)): + cur_col_name = df.columns[a] + df = df.rename(columns={cur_col_name:f"x{a-2}"}) + + + del a + + return df + +@append_global_iteration_lambda_outcmaes +def load_xrecentbest_cmaes(path:Union[str,Path])->pd.DataFrame: + r""" + This is just a function which calls the `xrecentbest.dat` generated from a CMA-ES run + by using `pycmaes` (The library from Niko Hansen) + """ + + if not path.endswith("xrecentbest.dat"): + raise AttributeError("The path does not correspond to the `xrecentbest.dat` file", + name="path", + obj=path) + + # These are the tile columns + # NOTE:This file does require computing some dimension of the problem + + TITLE_COLUMNS:dict = {"iteration":int, + "evaluation":int, + "sigma":int, + "0":int, + "fitness":int, + "xbest":object} + + + with open(file=path,mode="r",encoding="utf-8") as f: + _ = f.readline() + second_line = f.readline() + + # Get the number of variables by performing the split + items = second_line.split(" ") + + #close the IOStream + f.close() + + + ncols_in_file:int = len(items) + problem_dimension = ncols_in_file - len(TITLE_COLUMNS) + 1 + + del items, second_line + + # Extract the void columns + void_cols = [] + non_void_idxs = [] + + for idx, title in enumerate(TITLE_COLUMNS.keys()): + if title.startswith("0"): + void_cols.append(title) + else: + non_void_idxs.append(idx) + + for idx in range(6,5+problem_dimension): + non_void_idxs.append(idx) + + # Generate the `pandas` Dataframe + df = pd.read_csv(filepath_or_buffer=path, + sep=" ", + skiprows=0, + #header=0, + #names=TITLE_COLUMNS.keys(), + index_col=False, + usecols=non_void_idxs, + #dtype=TITLE_COLUMNS, + ) + + for a in range(4): + cur_col_name = df.columns[a] + + # Reassign the name + curkey = [*TITLE_COLUMNS][non_void_idxs[a]] + df = df.rename(columns={cur_col_name:curkey}) + + for a in range(4,(4+problem_dimension)): + cur_col_name = df.columns[a] + df = df.rename(columns={cur_col_name:f"x{a-4}"}) + + + del a + + return df + +def load_cmaes_file_as_dataframe(path:Union[str,Path])->pd.DataFrame: + + if path.endswith("fit.dat"): + df = load_fit_cmaes(path) + elif path.endswith("xmean.dat"): + df = load_xmean_cmaes(path) + elif path.endswith("xrecentbest.dat"): + df = load_xrecentbest_cmaes(path) + else: + raise AttributeError("The function is not implemented for other type of file") + + + return df + +def load_particular_data(foldername:Union[str,Path])->dict: + r""" + This function receives a path of a folder and returns a list comprised of loaded dataframes with + information about a particular run identified by the folder + + Args: + ------------------- + - foldername: `Union[str,Path]`: A path with the information of a run + + Returns: + ------------------ + - A list of Dataframes with the information of the run + """ + + foldername = _convert_2_path_object(foldername) + + IOH_file:str = None + + file_struct = os.listdir(foldername.absolute()) + + # Get the IOH Profiler File within + for filename in file_struct: + if filename.endswith(".json"): + # Assign the filename + IOH_file = os.path.join(foldername.absolute(),filename) + + # Break the loop + break + + del filename + + cmaes_path = os.path.join(foldername.absolute(),"outcmaes") + cmaes_files = os.listdir(cmaes_path) + + + if not os.path.exists(cmaes_path): + raise FileNotFoundError() + + # Generate the IOH object + ioh_obj:IOH_Parser = IOH_Parser(IOH_file) + + #Generate a names array + names_arr:list = ["evaluations"] + + # Generate a list that stores dataframes + dataframes_arr:List[pd.DataFrame] = [ioh_obj.return_complete_table_per_instance(0)] + + for cmaes_file in cmaes_files: + # Loop all over the files + if cmaes_file in RELEVANT_OUTCMAES: + dataframes_arr.append(load_cmaes_file_as_dataframe(os.path.join(cmaes_path,cmaes_file))) + names_arr.append(cmaes_file.removesuffix(".dat")) + + + return_dict = {name_i: dataf_i for name_i,dataf_i in list(zip(names_arr,dataframes_arr))} + + return return_dict + +def plot_run_iterations_on_contour(trial_path:Union[str,Path],df_dict:dict)->None: + r""" + This function is meant to plot the results of each iteration of a run. + + Args: + --------------- + - trial_path: `Union[str,Path]`: Path indicating the repository of lookup folder of the run + - df_dict: `dict` : A dictionary pointing to the dataframes with relevant data of corresponding run + """ + + import matplotlib.pyplot as plt + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(trial_path) + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + run_:int = data_dict['run'] + + if not os.path.exists(os.path.join(os.getcwd(),"Plots_Definite",func_type,inv_mode,cur_alg,f"f{func_id}",f"Run_{run_}")): + # Generate a path + path_:Path = Path(os.path.join(os.getcwd(),"Plots_Definite",func_type,inv_mode,cur_alg,f"f{func_id}",f"Run_{run_}")) + path_.mkdir(parents=True) + + # Find the main folder data of two-dimensional problems + data_2D_prob:str = os.path.join(ROOT_TWO_DIMENSIONAL_PROBLEMS_REPO,func_type,inv_mode,f"f{func_id}") + + # load the data to generate the contour + data_file_path:str = os.path.join(data_2D_prob,"data.npy") + with open(data_file_path,'rb') as ff: + xx = np.load(ff) + yy = np.load(ff) + zz = np.load(ff) + + # plot the contour first + fig1, ax2 = plt.subplots(layout='constrained') + CS = ax2.contourf(xx, yy, zz, 30, cmap=plt.cm.coolwarm) + fig1.colorbar(CS, ax=ax2, shrink=0.9) + + + # Plot the labels + ax2.set_xlabel(f"$x_{0}$") + ax2.set_ylabel(f"$x_{1}$") + #ax2.set_title(f"Function: {func_name}, mode: {inv_mode} \n Run: {run_}") + #ax2.legend(loc="best") + + # Perform a loop all over the fit.dat dataset + fit_dataset:pd.DataFrame = df_dict['fit'] + evaluations_dataset:pd.DataFrame = df_dict['evaluations'] + + cur_pts, = ax2.plot([0,1],[0,1],marker="D", color="black", markerfacecolor="black" ,linestyle='None',) + #plt.ion() + for row_t in fit_dataset.itertuples(): + # Extract the current iteration, evaluation and lambda + cur_iter = int(row_t.Index + 1) + cur_max_evaluation = int(row_t.evaluation) + cur_lambda = int(row_t.lamda) + + init_eval_idx = int(cur_max_evaluation-cur_lambda+1) + final_eval_idx = int(cur_max_evaluation) + + # Slice the evaluations dataset with the evaluations indices + relevant_evaluations = evaluations_dataset[(evaluations_dataset['evaluations'] >= init_eval_idx) & (evaluations_dataset['evaluations'] <= final_eval_idx)] + + cur_x0, cur_x1 = (relevant_evaluations['x0'].to_numpy(), + relevant_evaluations['x1'].to_numpy()) + ax2.set_title(f"Iteration: {cur_iter} lambda: {cur_lambda} ") + #current_points = ax2.scatter(x=cur_x0,y=cur_x1,marker=".", color="black") + cur_pts.set_xdata(cur_x0) + cur_pts.set_ydata(cur_x1) + fig1.canvas.draw() + + + + plt.savefig(os.path.join(os.getcwd(),"Plots_Definite",func_type,inv_mode,cur_alg,f"f{func_id}",f"Run_{run_}", + f'plot_iteration_{cur_iter}.pdf'),format="pdf",dpi=100) + #fig1.canvas.flush_events() + + +def load_experiment_file(exp_file:Union[str,Path])->pd.DataFrame: + r""" + This function is meant to just load the experiment setup file into memory + + Args: + ------------------- + exp_file: `Union[str,Path]`: This is a path indicating the route to load the experiment setup file + """ + + # Convert the file to path + path_:Path = _convert_2_path_object(exp_file) + + if path_.exists() and path_.suffix in ([".xlsx",".ods"]): + df:pd.DataFrame = pd.read_excel(path_.absolute(),header=0) + elif path_.exists() and path_.suffix in ([".txt",".dat",".csv"]): + df:pd.DataFrame = pd.read_csv(path_.absolute(),header=0,sep=",") + else: + raise ValueError("The file is not an Excel Workbook or a text datasheet") + + return df + + +def _classify_run_folders(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + mode: int = 1) -> Union[List[List[str]], + List[List[List[str]]], + List[List[List[List[str]]]] + ]: + r""" + This function performs classification of files based on a specific mode in order to generate corresponding results. + + Args: + ---------- + - exp_file: `Union[str, Path]`: Path or string to the experiment file. + - root_experiment_repo: `Union[str, Path]`: Path or string to the root directory of the experiment repository. + - mode: `int`: Integer specifying the mode of classification (default is 1). + + Returns: + ---------- + - A list of lists, where each sublist contains classified files or directories based on the specified mode. + + Raises: + ---------- + - AttributeError: If the given root_experiment_repo path does not exist. + """ + + from itertools import product + + def mode_1(path_obj:Path, + exp_df:pd.DataFrame)->List[List[str]]: + r""" + This is the definition of the classification mode 1; + The lists will consist of having type/invariance_mode/function/algorithm/groups of 4 sigmas + (same seed) + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + iterable_of_function_algorithm = list(product(PROBLEMS_MODULES,INVARIANCE_MODES,ALGORITHMS_MODULES,FID_LIMITS)) + unique_seeds = pd.unique(exp_df['Seed']) + + list_of_lists:list = [] + + for prob_mod_i,inv_mod_i, alg_mod_i, fid_i in iterable_of_function_algorithm: + + # Iterate over the same seed runs + for seed in unique_seeds: + # Get the runs with 'iso-seed' + id_df:pd.DataFrame = exp_df[exp_df["Seed"]==seed] + runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + # Initialize the list + partial_list:list = [] + + for i_run in runs_to_iterate: + # Build-up the string to search + search_string:str = os.path.join(path_obj.absolute(), + prob_mod_i, + inv_mod_i, + alg_mod_i, + str(f"f{fid_i}"),f"Run_{i_run}").replace("\\",'/') + + # Look for matching substrings + partial_list.append(search_string) + + # Append the list + list_of_lists.append(partial_list) + + + + + return list_of_lists + + def mode_2(path_obj:Path, + exp_df:pd.DataFrame)->List[List[str]]: + r""" + This is the definition of the classification mode 2; + The lists will consist of having type/invariance_mode/function/algorithm/groups of 30 runs + (same seed) + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + iterable_of_function_algorithm = list(product(PROBLEMS_MODULES, + INVARIANCE_MODES, + ALGORITHMS_MODULES, + FID_LIMITS)) + unique_sigmas = pd.unique(exp_df['Step_Size']) + + list_of_lists:list = [] + + for prob_mod_i,inv_mod_i, alg_mod_i, fid_i in iterable_of_function_algorithm: + + # Iterate over the same seed runs + for sigma in unique_sigmas: + # Get the runs with 'iso-sigma' + id_df:pd.DataFrame = exp_df[exp_df['Step_Size']==sigma] + runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + # Initialize the list + partial_list:list = [] + + for i_run in runs_to_iterate: + # Build-up the string to search + search_string:str = os.path.join(path_obj.absolute(), + prob_mod_i, + inv_mod_i, + alg_mod_i, + str(f"f{fid_i}"),f"Run_{i_run}").replace("\\",'/') + + # Look for matching substrings + partial_list.append(search_string) + + # Append the list + list_of_lists.append(partial_list) + + + return list_of_lists + + + def mode_3(path_obj:Path, + exp_df:pd.DataFrame)->List[List[List[str]]]: + r""" + This is the definition of the classification mode 3; + The lists will consist of having type/invariance_mode/function/algorithm/groups of 120 runs + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + iterable_of_function_algorithm = list(product(PROBLEMS_MODULES, + INVARIANCE_MODES, + ALGORITHMS_MODULES, + FID_LIMITS)) + unique_sigmas = pd.unique(exp_df['Step_Size']) + + list_of_lists:list = [] + + for prob_mod_i,inv_mod_i, alg_mod_i, fid_i in iterable_of_function_algorithm: + + # Initialize an empty list to store the iso-sigma_runs + partial_high_list:list = [] + + # Iterate over the same seed runs + for sigma in unique_sigmas: + # Get the runs with 'iso-sigma' + id_df:pd.DataFrame = exp_df[exp_df['Step_Size']==sigma] + runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + # Initialize the list + partial_list:list = [] + + for i_run in runs_to_iterate: + # Build-up the string to search + search_string:str = os.path.join(path_obj.absolute(), + prob_mod_i, + inv_mod_i, + alg_mod_i, + str(f"f{fid_i}"),f"Run_{i_run}").replace("\\",'/') + + # Look for matching substrings + partial_list.append(search_string) + + partial_high_list.append(partial_list) + # Append the list + list_of_lists.append(partial_high_list) + + return list_of_lists + + def mode_4(path_obj:Path, + exp_df:pd.DataFrame)->List[List[str]]: + r""" + This is the definition of the classification mode 4; + The lists will consist of having type/invariance_mode/function base, + + Then the sublists will contain the pairs algorithm/run + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + iterable_of_function = list(product(PROBLEMS_MODULES, + INVARIANCE_MODES, + FID_LIMITS)) + all_runs = pd.unique(exp_df['Run']) + + list_of_lists:list = [] + + for prob_mod_i,inv_mod_i, fid_i in iterable_of_function: + + # Iterate over the same seed runs + for i_run in all_runs: + + # Initialize the list + partial_list = [] + # Loop all over the algorithms + for alg_mod_i in ALGORITHMS_MODULES: + + + # Build-up the string to search + search_string:str = os.path.join(path_obj.absolute(), + prob_mod_i, + inv_mod_i, + alg_mod_i, + str(f"f{fid_i}"),f"Run_{i_run}").replace("\\",'/') + + # Look for matching substrings + partial_list.append(search_string) + + # Append the list + list_of_lists.append(partial_list) + + return list_of_lists + + def mode_5(path_obj:Path, + exp_df:pd.DataFrame)->List[List[List[List[str]]]]: + r""" + This is the definition of the classification mode 5; + The lists will consist of having type/invariance_mode/function base, + + Then the sublists will contain the pairs algorithm and then contain all the runs. + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + iterable_of_function = list(product(PROBLEMS_MODULES, + INVARIANCE_MODES, + FID_LIMITS)) + unique_sigma = pd.unique(exp_df['Step_Size']) + + list_of_lists:list = [] + + for prob_mod_i,inv_mod_i, fid_i in iterable_of_function: + + # Initialize an empty list to store the iso-sigma_runs + partial_high_list:list = [] + # Iterate over the same seed runs + for i_sigma in unique_sigma: + + sigma_list:list = [] + + partial_df = exp_df[exp_df["Step_Size"]==i_sigma] + list_runs:pd.DataFrame = partial_df['Run'] + # Loop all over the algorithms + for alg_mod_i in ALGORITHMS_MODULES: + + # Initialize the list + partial_list = [] + + # Loop all over the runs + + for i_run in list_runs.to_numpy(dtype=int): + # Build-up the string to search + search_string:str = os.path.join(path_obj.absolute(), + prob_mod_i, + inv_mod_i, + alg_mod_i, + str(f"f{fid_i}"),f"Run_{i_run}").replace("\\",'/') + + # Look for matching substrings + partial_list.append(search_string) + + sigma_list.append(partial_list) + partial_high_list.append(sigma_list) + + # Append the list + list_of_lists.append(partial_high_list) + + return list_of_lists + + + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + root_experiment_repo_: Path = _convert_2_path_object(root_experiment_repo) + + # Check if the root experiment repository exists + if not root_experiment_repo_.exists(): + raise AttributeError("The given path does not exist", + name="root_experiment_repo", + obj=root_experiment_repo) + + #for ii, ff in enumerate(list_generator): + #rint(ii, ff) + # a = 1 + + if mode==1: + return mode_1(root_experiment_repo_, + exp_file_df) + elif mode==2: + return mode_2(root_experiment_repo_, + exp_file_df) + elif mode==3: + return mode_3(root_experiment_repo_, + exp_file_df) + + elif mode==4: + return mode_4(root_experiment_repo_, + exp_file_df) + + elif mode==5: + return mode_5(root_experiment_repo_, + exp_file_df) + + + +def plot_mode_1(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_1"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=1) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + # Initialize a plot object + seed:Union[None,int] = None + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_ylabel("Best so far") + #ax1.set_yscale('log') + for idx_curve,iFile in enumerate(sublist): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + run_:int = data_dict['run'] + + # Get the sigma/step size + cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] + + if idx_curve == 0: + # Match the run with the seed + seed = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Seed"].to_numpy().ravel()[0] + + # plot the curves + ax1.plot(evaluations_dataset.loc[:,["evaluations"]].to_numpy(), + evaluations_dataset.loc[:,["raw_y_best"]].to_numpy(), + label=f"$\sigma_0={cur_step_size}$") + + + ax1.set_title(f"Function: {func_name}, Seed: {str(seed)}") + ax1.legend(loc='best') + + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + cur_alg, + f"f{func_id}", + str(seed))) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + +def plot_mode_2(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_2"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=2) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_ylabel("Best so far") + + #ax1.set_yscale('log') + for idx_curve,iFile in enumerate(sublist): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + run_:int = data_dict['run'] + + + if idx_curve == 0: + # Match the run with the step size + cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] + step_size_index:int = exp_file_df[exp_file_df["Run"]==run_].index.to_numpy().ravel()[0] + 1 + + # plot the curves + ax1.plot(evaluations_dataset.loc[:,["evaluations"]].to_numpy(), + evaluations_dataset.loc[:,["raw_y_best"]].to_numpy(), + color='black') + + + ax1.set_title(f"Function: {func_name}, Sigma: {str(cur_step_size)}, Runs Number: {len(sublist)}") + + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + cur_alg, + f"f{func_id}", + f"Step_Size_{step_size_index}")) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + + +def plot_mode_3(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_3"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=3) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_ylabel("Best so far") + + #ax1.set_yscale('log') + for idx_sigma,i_list_sigma in enumerate(sublist): + + # Compressed Evaluations df + df_compressed:Union[None,pd.DataFrame] = None # Initialize + + for idx_file, iFile in enumerate(i_list_sigma): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + run_:int = data_dict['run'] + + if idx_file == 0: + df_compressed = evaluations_dataset.copy() + # Reassign the runs column + df_compressed["run"] = df_compressed["run"].replace(1, run_) + else: + # Change the run number + evaluations_dataset["run"] = evaluations_dataset["run"].replace(1, run_) + + # append to last + df_compressed = pd.concat([df_compressed, evaluations_dataset],axis=0) + + + + # if idx_curve == 0: + # # Match the run with the step size + # cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] + # step_size_index:int = exp_file_df[exp_file_df["Run"]==run_].index.to_numpy().ravel()[0] + 1 + + # Get the current sigma + cur_sigma:float = exp_file_df.loc[idx_sigma,"Step_Size"] + + # Get a consolidated average and standard deviation + mean_df, std_df, count_df = (df_compressed.groupby(['evaluations']).mean(), + df_compressed.groupby(['evaluations']).std(), + df_compressed.groupby(['evaluations']).count()) + + r""" + mean_arr,std_arr, count_arr = (data_array[idx].groupby(['evaluations']).mean(), + data_array[idx].groupby(['evaluations']).std(), + data_array[idx].groupby(['evaluations']).count()) + + ax_ptr.plot(mean_arr.index.to_numpy(),-1*mean_arr['Objective'],label=f"{DIMENSIONS[idx]}D") + up_bound = -1*mean_arr['Objective'].to_numpy() + 1.96*std_arr['Objective'].to_numpy()/np.sqrt(count_arr['Objective'].to_numpy()) + lo_bound = -1*mean_arr['Objective'].to_numpy() - 1.96*std_arr['Objective'].to_numpy()/np.sqrt(count_arr['Objective'].to_numpy()) + ax_ptr.fill_between(x=mean_arr.index.to_numpy(),y1 = up_bound, y2 = lo_bound, alpha=0.2) + + """ + + # plot the curves + ax1.plot(mean_df.index.to_numpy(), + mean_df['raw_y_best'].to_numpy(), + label=f"$\sigma_0=${cur_sigma}") + + up_bound = mean_df['raw_y_best'].to_numpy() + 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + lo_bound = mean_df['raw_y_best'].to_numpy() - 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + + ax1.fill_between(x=mean_df.index.to_numpy(), + y1= up_bound, + y2= lo_bound, + alpha=0.2) + + + ax1.set_title(f"Function: {func_name}, Algorithm: {cur_alg}") + + ax1.legend(loc="best") + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + cur_alg, + f"f{func_id}", + )) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + +def plot_mode_4(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_4"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=4) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_ylabel("Best so far") + + actual_seed = None + + for idx_file, iFile in enumerate(sublist): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + run_:int = data_dict['run'] + + + if idx_file == 0: + # Match the run with the step size + cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] + step_size_index:int = exp_file_df[exp_file_df["Run"]==run_].index.to_numpy().ravel()[0] + 1 + actual_seed = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Seed"].to_numpy().ravel()[0] + + # plot the curves + ax1.plot(evaluations_dataset.loc[:,["evaluations"]].to_numpy(), + evaluations_dataset.loc[:,["raw_y_best"]].to_numpy(), + label=cur_alg) + + + ax1.set_title(f"Function: {func_name}, Seed:{actual_seed}, $\sigma_0=${cur_step_size}") + + ax1.legend(loc="best") + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + f"f{func_id}", + f"Run_{run_}" + )) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + +def plot_mode_5(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_5"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=5) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + all_sigmas = pd.unique(exp_file_df['Step_Size']) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + + + #ax1.set_yscale('log') + for idx_sigma,i_list_sigma in enumerate(sublist): + + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_ylabel("Best so far") + + for idx_alg, i_list_alg in enumerate(i_list_sigma): + + # Compressed Evaluations df + df_compressed:Union[None,pd.DataFrame] = None # Initialize + + for idx_file, iFile in enumerate(i_list_alg): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + run_:int = data_dict['run'] + + if idx_file == 0: + df_compressed = evaluations_dataset.copy() + # Reassign the runs column + df_compressed["run"] = df_compressed["run"].replace(1, run_) + else: + # Change the run number + evaluations_dataset["run"] = evaluations_dataset["run"].replace(1, run_) + + # append to last + df_compressed = pd.concat([df_compressed, evaluations_dataset],axis=0) + + # Get the current sigma + cur_sigma:float = all_sigmas[idx_sigma] + + # Get a consolidated average and standard deviation + mean_df, std_df, count_df = (df_compressed.groupby(['evaluations']).mean(), + df_compressed.groupby(['evaluations']).std(), + df_compressed.groupby(['evaluations']).count()) + + # plot the curves + ax1.plot(mean_df.index.to_numpy(), + mean_df['raw_y_best'].to_numpy(), + label=cur_alg) + + up_bound = mean_df['raw_y_best'].to_numpy() + 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + lo_bound = mean_df['raw_y_best'].to_numpy() - 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + + ax1.fill_between(x=mean_df.index.to_numpy(), + y1= up_bound, + y2= lo_bound, + alpha=0.2) + + + ax1.set_title(f"Function: {func_name}, $\sigma_0=$ {cur_sigma}") + + ax1.legend(loc="best") + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + f"f{func_id}", + f"Step_size_{idx_sigma+1}" + )) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/injection/f10/Run_1" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/rejection_sampling/f10/Run_4" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/pure/f10/Run_1" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/pure/f8/Run_6" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/rejection_sampling/f8/Run_6" + +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOBWithSubspace/average/rejection_sampling/f6/Run_6" +#trial_path:str = "C:/Users/iolar/Downloads/Loaded_ModularExperiments/NonBBOB/average/pure/f1/Run_100" +#trial_path:str = "C:/Users/iolar/Downloads/Loaded_ModularExperiments/NonBBOB/average/injectioN/f7/Run_1" + +#list_ = load_particular_data(trial_path) +#print(list_['fit']) +#print(list_['evaluations']) + +#plot_run_iterations_on_contour(trial_path,list_) + +#resulting_list:list = classify_run_folders( +# exp_file="C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_1.csv", +## root_experiment_repo="C:/Users/iolar/Downloads/Loaded_ModularExperiments", + # mode=1) + +plot_mode_5("C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_1.csv", + "C:/Users/iolar/Downloads/Loaded_ModularExperiments") + + + diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/2d_postprocessing_plotting_2.py b/mylib/lib_BO_torch_repo/GlobalUtilities/2d_postprocessing_plotting_2.py new file mode 100644 index 0000000..3374b0e --- /dev/null +++ b/mylib/lib_BO_torch_repo/GlobalUtilities/2d_postprocessing_plotting_2.py @@ -0,0 +1,1716 @@ +r""" +This is a module which will be used to generate convergence plots for 2D Modular problems + +--------------------- +NOTE: +This is just a template, which will be further developed in the future for other cases +-------------------- +""" + +__author__ = ["Iván Olarte Rodríguez"] +__institute__ = ["Leiden Institute of Advanced Computer Science"] + + +# Import libraries/modules +import os, sys +from IOH_parser import IOH_Parser # Parser defined within this context +from pathlib import Path +import numpy as np +import pandas as pd +from typing import Union, List, Callable, Iterable, Dict + +#Converters +__idx_space_dict:dict = {1:"bimodal", + 2:"multimodal", + 3:"plateau", + 4:"ackley", + 5:"gramacy_lee", + 6:"levy", + 7:"rastrigin", + 8:"schwefel", + 9:"sum_different_powers", + 10:"forrester", + 11:"sphere", + 12:"alpine", + 13:"styblinski_tank", + 14:"salomon"} + + +### ------------------ +### CONSTANTS +### ------------------ + +# IMMUTABLE +PROBLEMS_MODULES:tuple = ("NonBBOB","NonBBOBWithSubspace") +ALGORITHMS_MODULES:tuple = ("injection","pure","rejection_sampling") +OUTCMAES_FILENAMES:tuple = ("axlen.dat", + "axlencorr.dat", + "axlenprec.dat", + "fit.dat", + "stddev.dat", + "xmean.dat", + "xrecentbest.dat") + +INVARIANCE_MODES:tuple = ("average","maximum") + +TABU_ZONES_TUPLE:tuple = ("OnlyTabu","WithTabuAndIntrinsic") + +RELEVANT_OUTCMAES:list = [OUTCMAES_FILENAMES[3],OUTCMAES_FILENAMES[5],OUTCMAES_FILENAMES[6]] +FID_LIMITS:list = [5,7,8,10,12,14] + +# TODO: Filled by the user +ROOT_MODULAR_EXPERIMENTS:str = os.path.join(os.getcwd(),"ModularExperiments") +ROOT_TWO_DIMENSIONAL_PROBLEMS_REPO:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/Two_Dimensional" + + +### ------------------- +### VARIOUS FUNCTIONS +### ------------------- + +def _convert_2_path_object(path_:Union[str,Path])->Path: + r""" + This is a helper function to use to convert path_strings to Path + + Args: + -------------------- + - path_: `Union[str,Path]`: A string path to convert + + Returns: + - A ``Path` object + """ + + if isinstance(path_,Path): + return path_ + elif isinstance(path_,str): + return Path(path_) + else: + raise AttributeError("given path not an instance of Path or a string", name="path_",obj=path_) + +def _return_identifiers_from_file_path(path_:Union[str,Path])->Dict: + r""" + This is a function which just identifies all the problem parameters given a path. + + Args: + ------------------- + - path_: `Union[str,Path]`: A path object identifying a 'Run_' instance + + Returns: + ------------------- + A dictionary with the keys; + - func_type:`str`: The function type, which should have two options: ("NonBBOB","NonBBOBWithSubspace") + - inv_mode:`str` = The invariance mode: ('average', 'maximum) + - cur_alg:`str` = The algorithm applied to the problem ('pure','injection','rejection_sampling') + - func_id:`int` = The id alias of the function (so far an integer between 1 to 14) + - func_name:`str` = The actual name of the function (check the namespace `__idx_space_dict`) + - tabu_definition:`Union[str,None]` = The definition of the tabu zone (if it exists) + - run:`int` = The integer mapping a run number + """ + + # Break down the path to extract information regarding the function + trial_path:Path = _convert_2_path_object(path_) + + # Transform the path to posix ("/") + trial_path_ = trial_path.as_posix() + + # Instantiate a variable to check if the path contains a tabu zone + tabu_zones_search:bool = False + + # Split all the datastructure + data_struct_full:list = trial_path_.split("/") + + for idx,part in enumerate(data_struct_full): + if part in TABU_ZONES_TUPLE: + tabu_zones_search = True + break + + if not tabu_zones_search: + data_struct:list = data_struct_full[-5:] + + # Get the function type, invariance mode, function id and run + func_type:str = data_struct[0] + inv_mode:str = data_struct[1] + cur_alg:str = data_struct[2] + func_id:int = int(data_struct[3].replace("f","")) + func_name:str = __idx_space_dict[func_id] + run_:int = int(data_struct[4].replace("Run_","")) + + + return {"func_type":func_type, + "inv_mode":inv_mode, + "cur_alg":cur_alg, + "func_id":func_id, + "func_name":func_name, + "tabu_definition":None, + "run":run_} + else: + data_struct:list = data_struct_full[-6:] + + # Get the function type, invariance mode, function id and run + func_type:str = data_struct[0] + inv_mode:str = data_struct[1] + cur_alg:str = data_struct[2] + tabu_definition:str = data_struct[3] + func_id:int = int(data_struct[4].replace("f","")) + func_name:str = __idx_space_dict[func_id] + run_:int = int(data_struct[5].replace("Run_","")) + + + return {"func_type":func_type, + "inv_mode":inv_mode, + "cur_alg":cur_alg, + "func_id":func_id, + "func_name":func_name, + "tabu_definition":tabu_definition, + "run":run_} + + + + +def append_global_iteration_lambda_outcmaes(outcmaes_func:Callable)->Callable: + r""" + This is a function which receives an outcmaes importing function (ones which takes a saved) + cmaes file and converts to a `pandas dataframe` and appends two columns to the dataframe, + namely the number of restarts and the current population size. + """ + + def append_restart_column(init_df:pd.DataFrame)->pd.DataFrame: + # Extract the iteration column + init_df['restart_count'] = (init_df['iteration'] == 1).cumsum()-1 + + return init_df + + def append_lambda_column(init_df:pd.DataFrame)->pd.DataFrame: + init_df['lamda'] = init_df.groupby('restart_count')['evaluation'].diff() + init_df['lamda'] = init_df['lamda'].fillna(init_df.groupby('restart_count')['lamda'].shift(-1)) + + init_df['lamda'] = init_df['lamda'].fillna(init_df['lamda'].shift(1) * 2) + + init_df['lamda'] =init_df['lamda'].astype(int) + return init_df + + def load_function(data_path:Union[str,Path])->pd.DataFrame: + cur_df = outcmaes_func(data_path) + cur_df_1 = append_restart_column(cur_df) + cur_df_2 = append_lambda_column(cur_df_1) + + return cur_df_2 + + return load_function + +@append_global_iteration_lambda_outcmaes +def load_fit_cmaes(path:Union[str,Path])->pd.DataFrame: + r""" + This is just a function which calls the `fit.dat` generated from a CMA-ES run + by using `pycmaes` (The library from Niko Hansen) + """ + + if not path.endswith("fit.dat"): + raise AttributeError("The path does not correspond to the `fit.dat` file", + name="path", + obj=path) + + # These are the tile columns + # This file does not require computing some dimension of the problem + TITLE_COLUMNS:dict = {"iteration":int, + "evaluation":int, + "sigma":float, + "axis_ratio":float, + "bestever":float, + "best":float, + "median":float, + "worst_objective_function_value":float} + + # Generate the `pandas` Dataframe + import warnings + + warnings.simplefilter(action='ignore', category=pd.errors.ParserWarning) + with warnings.catch_warnings(): + df = pd.read_csv(path, + sep=" ", + header=None, + skiprows=[0], + names=TITLE_COLUMNS.keys(), + index_col=False, + dtype=TITLE_COLUMNS, + ) + + return df + +@append_global_iteration_lambda_outcmaes +def load_xmean_cmaes(path:Union[str,Path])->pd.DataFrame: + r""" + This is just a function which calls the `xmean.dat` generated from a CMA-ES run + by using `pycmaes` (The library from Niko Hansen) + """ + + if not path.endswith("xmean.dat"): + raise AttributeError("The path does not correspond to the `xmean.dat` file", + name="path", + obj=path) + + # These are the tile columns + # NOTE:This file does require computing some dimension of the problem + + TITLE_COLUMNS:dict = {"iteration":int, + "evaluation":int, + "void0":int, + "void1":int, + "void2":int, + "xmean":object} + + with open(file=path,mode="r",encoding="utf-8") as f: + _ = f.readline() + second_line = f.readline() + + # Get the number of variables by performing the split + items = second_line.split(" ") + + #close the IOStream + f.close() + + + ncols_in_file:int = len(items) + problem_dimension = ncols_in_file - len(TITLE_COLUMNS) + 1 + + del items, second_line + + # Extract the void columns + void_cols = [] + non_void_idxs = [] + + for idx, title in enumerate(TITLE_COLUMNS.keys()): + if title.startswith("void"): + void_cols.append(title) + else: + non_void_idxs.append(idx) + + for idx in range(6,5+problem_dimension): + non_void_idxs.append(idx) + + # Generate the `pandas` Dataframe + df = pd.read_csv(filepath_or_buffer=path, + sep=" ", + skiprows=0, + #header=0, + #names=TITLE_COLUMNS.keys(), + index_col=False, + usecols=non_void_idxs, + #dtype=TITLE_COLUMNS, + ) + + for a in range(2): + cur_col_name = df.columns[a] + + # Reassign the name + curkey = [*TITLE_COLUMNS][a] + df = df.rename(columns={cur_col_name:curkey}) + + for a in range(2,(2+problem_dimension)): + cur_col_name = df.columns[a] + df = df.rename(columns={cur_col_name:f"x{a-2}"}) + + + del a + + return df + +@append_global_iteration_lambda_outcmaes +def load_xrecentbest_cmaes(path:Union[str,Path])->pd.DataFrame: + r""" + This is just a function which calls the `xrecentbest.dat` generated from a CMA-ES run + by using `pycmaes` (The library from Niko Hansen) + """ + + if not path.endswith("xrecentbest.dat"): + raise AttributeError("The path does not correspond to the `xrecentbest.dat` file", + name="path", + obj=path) + + # These are the tile columns + # NOTE:This file does require computing some dimension of the problem + + TITLE_COLUMNS:dict = {"iteration":int, + "evaluation":int, + "sigma":int, + "0":int, + "fitness":int, + "xbest":object} + + + with open(file=path,mode="r",encoding="utf-8") as f: + _ = f.readline() + second_line = f.readline() + + # Get the number of variables by performing the split + items = second_line.split(" ") + + #close the IOStream + f.close() + + + ncols_in_file:int = len(items) + problem_dimension = ncols_in_file - len(TITLE_COLUMNS) + 1 + + del items, second_line + + # Extract the void columns + void_cols = [] + non_void_idxs = [] + + for idx, title in enumerate(TITLE_COLUMNS.keys()): + if title.startswith("0"): + void_cols.append(title) + else: + non_void_idxs.append(idx) + + for idx in range(6,5+problem_dimension): + non_void_idxs.append(idx) + + # Generate the `pandas` Dataframe + df = pd.read_csv(filepath_or_buffer=path, + sep=" ", + skiprows=0, + #header=0, + #names=TITLE_COLUMNS.keys(), + index_col=False, + usecols=non_void_idxs, + #dtype=TITLE_COLUMNS, + ) + + for a in range(4): + cur_col_name = df.columns[a] + + # Reassign the name + curkey = [*TITLE_COLUMNS][non_void_idxs[a]] + df = df.rename(columns={cur_col_name:curkey}) + + for a in range(4,(4+problem_dimension)): + cur_col_name = df.columns[a] + df = df.rename(columns={cur_col_name:f"x{a-4}"}) + + + del a + + return df + +def load_cmaes_file_as_dataframe(path:Union[str,Path])->pd.DataFrame: + + if path.endswith("fit.dat"): + df = load_fit_cmaes(path) + elif path.endswith("xmean.dat"): + df = load_xmean_cmaes(path) + elif path.endswith("xrecentbest.dat"): + df = load_xrecentbest_cmaes(path) + else: + raise AttributeError("The function is not implemented for other type of file") + + + return df + +def load_particular_data(foldername:Union[str,Path])->dict: + r""" + This function receives a path of a folder and returns a list comprised of loaded dataframes with + information about a particular run identified by the folder + + Args: + ------------------- + - foldername: `Union[str,Path]`: A path with the information of a run + + Returns: + ------------------ + - A list of Dataframes with the information of the run + """ + + foldername = _convert_2_path_object(foldername) + + IOH_file:str = None + + file_struct = os.listdir(foldername.absolute()) + + # Get the IOH Profiler File within + for filename in file_struct: + if filename.endswith(".json"): + # Assign the filename + IOH_file = os.path.join(foldername.absolute(),filename) + + # Break the loop + break + + del filename + + cmaes_path = os.path.join(foldername.absolute(),"outcmaes") + cmaes_files = os.listdir(cmaes_path) + + + if not os.path.exists(cmaes_path): + raise FileNotFoundError() + + # Generate the IOH object + ioh_obj:IOH_Parser = IOH_Parser(IOH_file) + + #Generate a names array + names_arr:list = ["evaluations"] + + # Generate a list that stores dataframes + dataframes_arr:List[pd.DataFrame] = [ioh_obj.return_complete_table_per_instance(0)] + + for cmaes_file in cmaes_files: + # Loop all over the files + if cmaes_file in RELEVANT_OUTCMAES: + dataframes_arr.append(load_cmaes_file_as_dataframe(os.path.join(cmaes_path,cmaes_file))) + names_arr.append(cmaes_file.removesuffix(".dat")) + + + return_dict = {name_i: dataf_i for name_i,dataf_i in list(zip(names_arr,dataframes_arr))} + + return return_dict + +def plot_run_iterations_on_contour(trial_path:Union[str,Path],df_dict:dict)->None: + r""" + This function is meant to plot the results of each iteration of a run. + + Args: + --------------- + - trial_path: `Union[str,Path]`: Path indicating the repository of lookup folder of the run + - df_dict: `dict` : A dictionary pointing to the dataframes with relevant data of corresponding run + """ + + import matplotlib.pyplot as plt + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(trial_path) + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + run_:int = data_dict['run'] + + if not os.path.exists(os.path.join(os.getcwd(),"Plots_Definite",func_type,inv_mode,cur_alg,f"f{func_id}",f"Run_{run_}")): + # Generate a path + path_:Path = Path(os.path.join(os.getcwd(),"Plots_Definite",func_type,inv_mode,cur_alg,f"f{func_id}",f"Run_{run_}")) + path_.mkdir(parents=True) + + # Find the main folder data of two-dimensional problems + data_2D_prob:str = os.path.join(ROOT_TWO_DIMENSIONAL_PROBLEMS_REPO,func_type,inv_mode,f"f{func_id}") + + # load the data to generate the contour + data_file_path:str = os.path.join(data_2D_prob,"data.npy") + with open(data_file_path,'rb') as ff: + xx = np.load(ff) + yy = np.load(ff) + zz = np.load(ff) + + # plot the contour first + fig1, ax2 = plt.subplots(layout='constrained') + CS = ax2.contourf(xx, yy, zz, 30, cmap=plt.cm.coolwarm) + fig1.colorbar(CS, ax=ax2, shrink=0.9) + + + # Plot the labels + ax2.set_xlabel(f"$x_{0}$") + ax2.set_ylabel(f"$x_{1}$") + #ax2.set_title(f"Function: {func_name}, mode: {inv_mode} \n Run: {run_}") + #ax2.legend(loc="best") + + # Perform a loop all over the fit.dat dataset + fit_dataset:pd.DataFrame = df_dict['fit'] + evaluations_dataset:pd.DataFrame = df_dict['evaluations'] + + cur_pts, = ax2.plot([0,1],[0,1],marker="D", color="black", markerfacecolor="black" ,linestyle='None',) + #plt.ion() + for row_t in fit_dataset.itertuples(): + # Extract the current iteration, evaluation and lambda + cur_iter = int(row_t.Index + 1) + cur_max_evaluation = int(row_t.evaluation) + cur_lambda = int(row_t.lamda) + + init_eval_idx = int(cur_max_evaluation-cur_lambda+1) + final_eval_idx = int(cur_max_evaluation) + + # Slice the evaluations dataset with the evaluations indices + relevant_evaluations = evaluations_dataset[(evaluations_dataset['evaluations'] >= init_eval_idx) & (evaluations_dataset['evaluations'] <= final_eval_idx)] + + cur_x0, cur_x1 = (relevant_evaluations['x0'].to_numpy(), + relevant_evaluations['x1'].to_numpy()) + ax2.set_title(f"Iteration: {cur_iter} lambda: {cur_lambda} ") + #current_points = ax2.scatter(x=cur_x0,y=cur_x1,marker=".", color="black") + cur_pts.set_xdata(cur_x0) + cur_pts.set_ydata(cur_x1) + fig1.canvas.draw() + + + + plt.savefig(os.path.join(os.getcwd(),"Plots_Definite",func_type,inv_mode,cur_alg,f"f{func_id}",f"Run_{run_}", + f'plot_iteration_{cur_iter}.pdf'),format="pdf",dpi=100) + #fig1.canvas.flush_events() + + +def load_experiment_file(exp_file:Union[str,Path])->pd.DataFrame: + r""" + This function is meant to just load the experiment setup file into memory + + Args: + ------------------- + exp_file: `Union[str,Path]`: This is a path indicating the route to load the experiment setup file + """ + + # Convert the file to path + path_:Path = _convert_2_path_object(exp_file) + + if path_.exists() and path_.suffix in ([".xlsx",".ods"]): + df:pd.DataFrame = pd.read_excel(path_.absolute(),header=0) + elif path_.exists() and path_.suffix in ([".txt",".dat",".csv"]): + df:pd.DataFrame = pd.read_csv(path_.absolute(),header=0,sep=",") + else: + raise ValueError("The file is not an Excel Workbook or a text datasheet") + + return df + + +def _classify_run_folders(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + mode: int = 1) -> Union[List[List[str]], + List[List[List[str]]], + List[List[List[List[str]]]] + ]: + r""" + This function performs classification of files based on a specific mode in order to generate corresponding results. + + Args: + ---------- + - exp_file: `Union[str, Path]`: Path or string to the experiment file. + - root_experiment_repo: `Union[str, Path]`: Path or string to the root directory of the experiment repository. + - mode: `int`: Integer specifying the mode of classification (default is 1). + + Returns: + ---------- + - A list of lists, where each sublist contains classified files or directories based on the specified mode. + + Raises: + ---------- + - AttributeError: If the given root_experiment_repo path does not exist. + """ + + from itertools import product + + def mode_1(path_obj:Path, + exp_df:pd.DataFrame)->List[List[str]]: + r""" + This is the definition of the classification mode 1; + The lists will consist of having type/invariance_mode/function/algorithm/Run + (same seed) + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + from collections import defaultdict + + # Preload all paths into memory + all_paths = [p.as_posix() for p in path_obj.rglob("*Run_*")] + + # Build an index for faster lookups: {fid -> {run_id -> [paths]}} + path_index = defaultdict(lambda: defaultdict(list)) + for path in all_paths: + # Extract fid and run_id from the path if it matches the pattern + parts = path.split("/") + for part in parts: + if part.startswith("f") and len(part) > 1 and part[1:].isdigit(): # Matches f{fid} + fid = int(part[1:]) + if "Run_" in path: + run_id = int(path.split("Run_")[1].split("/")[0]) + path_index[fid][run_id].append(path) + break + + # Iterate over FID limits and unique seeds + list_of_lists = [] + unique_seeds = pd.unique(exp_df['Seed']) + + for fid, seed in product(FID_LIMITS, unique_seeds): + # Filter runs for the current seed + id_df = exp_df[exp_df["Seed"] == seed] + runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + for i_run in runs_to_iterate: + # Retrieve paths from the preloaded index + partial_list = path_index[fid].get(i_run, [])[:3] + + # Append only if the partial_list is non-empty + if partial_list: + list_of_lists.append(partial_list) + + return list_of_lists + + def mode_2(path_obj:Path, + exp_df:pd.DataFrame)->List[List[str]]: + r""" + This is the definition of the classification mode 2; + The lists will consist of having type/invariance_mode/function/algorithm/groups of 30 runs + (same C) + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + from collections import defaultdict + + # Preload all paths into memory + all_paths = [p.as_posix() for p in path_obj.rglob("*Run_*")] + + # Build an index for faster lookups: {fid -> {run_id -> [paths]}} + path_index = defaultdict(lambda: defaultdict(list)) + for path in all_paths: + # Extract fid and run_id from the path if it matches the pattern + parts = path.split("/") + for part in parts: + if part.startswith("f") and len(part) > 1 and part[1:].isdigit(): # Matches f{fid} + fid = int(part[1:]) + + if "Run_" in path: + run_id = int(path.split("Run_")[1].split("/")[0]) + path_index[fid][run_id].append(path) + break + + # Iterate over FID limits and unique seeds + list_of_lists = [] + unique_cs = pd.unique(exp_df['RFC']) + + for fid, cc in product(FID_LIMITS, unique_cs): + # Filter runs for the current seed + id_df = exp_df[exp_df["RFC"] == cc] + runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + + for i_dx in range(3): + partial_list = [] + for i_run in runs_to_iterate: + + # Retrieve paths from the preloaded index + partial_list_ = path_index[fid].get(i_run, []) + + # Append only if the partial_list is non-empty + for elem in partial_list_: + if i_dx==0 or i_dx==1: + if TABU_ZONES_TUPLE[i_dx] in elem: + partial_list.append(elem) + else: + if TABU_ZONES_TUPLE[0] not in elem and TABU_ZONES_TUPLE[1] not in elem: + partial_list.append(elem) + + list_of_lists.append(partial_list) + + return list_of_lists + + + def mode_3(path_obj:Path, + exp_df:pd.DataFrame)->List[List[List[str]]]: + r""" + This is the definition of the classification mode 3; + The lists will consist of having type/invariance_mode/function/algorithm/groups of 120 runs + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + from collections import defaultdict + + # Preload all paths into memory + all_paths = [p.as_posix() for p in path_obj.rglob("*Run_*")] + + # Build an index for faster lookups: {fid -> {run_id -> [paths]}} + path_index = defaultdict(lambda: defaultdict(list)) + for path in all_paths: + # Extract fid and run_id from the path if it matches the pattern + parts = path.split("/") + for part in parts: + if part.startswith("f") and len(part) > 1 and part[1:].isdigit(): # Matches f{fid} + fid = int(part[1:]) + + if "Run_" in path: + run_id = int(path.split("Run_")[1].split("/")[0]) + path_index[fid][run_id].append(path) + break + + # Iterate over FID limits and unique seeds + list_of_lists = [] + unique_seed = pd.unique(exp_df['Seed']) + + for fid, seed in product(FID_LIMITS, unique_seed): + # Filter runs for the current seed + id_df = exp_df[exp_df["Seed"] == seed] + runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + + for i_dx in range(3): + partial_list = [] + for i_run in runs_to_iterate: + + # Retrieve paths from the preloaded index + partial_list_ = path_index[fid].get(i_run, []) + + # Append only if the partial_list is non-empty + for elem in partial_list_: + if i_dx==0 or i_dx==1: + if TABU_ZONES_TUPLE[i_dx] in elem: + partial_list.append(elem) + else: + if TABU_ZONES_TUPLE[0] not in elem and TABU_ZONES_TUPLE[1] not in elem: + partial_list.append(elem) + + list_of_lists.append(partial_list) + + return list_of_lists + + + def mode_4(path_obj:Path, + exp_df:pd.DataFrame)->List[List[List[str]]]: + r""" + This is the definition of the classification mode 4; + The lists will consist of having type/invariance_mode/function base, + + Then the sublists will contain the pairs algorithm/run + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + from collections import defaultdict + + # Preload all paths into memory + all_paths = [p.as_posix() for p in path_obj.rglob("*Run_*")] + + # Build an index for faster lookups: {fid -> {run_id -> [paths]}} + path_index = defaultdict(lambda: defaultdict(list)) + for path in all_paths: + # Extract fid and run_id from the path if it matches the pattern + parts = path.split("/") + for part in parts: + if part.startswith("f") and len(part) > 1 and part[1:].isdigit(): # Matches f{fid} + fid = int(part[1:]) + + if "Run_" in path: + run_id = int(path.split("Run_")[1].split("/")[0]) + path_index[fid][run_id].append(path) + break + + # Iterate over FID limits and unique seeds + list_of_lists = [] + unique_cs = pd.unique(exp_df['RFC']) + + # for fid in FID_LIMITS: + # for i_dx in range(3): + # i_dx_list = [] + # for cc in unique_cs: + # # Filter runs for the current seed + # id_df = exp_df[exp_df["RFC"] == cc] + # runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + # cc_list = [] + + # for i_run in runs_to_iterate: + # # Instantiate an empty list to store the iso-RFC runs + # partial_list = [] + + + # # Retrieve paths from the preloaded index + # partial_list_ = path_index[fid].get(i_run, []) + + # # Append only if the partial_list is non-empty + # for elem in partial_list_: + # if i_dx==0 or i_dx==1: + # if TABU_ZONES_TUPLE[i_dx] in elem: + # partial_list.append(elem) + # else: + # if TABU_ZONES_TUPLE[0] not in elem and TABU_ZONES_TUPLE[1] not in elem: + # partial_list.append(elem) + + # cc_list.append(partial_list) + + # i_dx_list.append(cc_list) + + + + # list_of_lists.append(cc_list) + + for fid, i_dx in product(FID_LIMITS,[*range(3)]): + partial_list_of_lists:list = [] + + for cc in unique_cs: + + # Filter runs for the current seed + id_df = exp_df[exp_df["RFC"] == cc] + runs_to_iterate = id_df['Run'].to_numpy(dtype=int) + + partial_list = [] + for i_run in runs_to_iterate: + # Instantiate an empty list to store the iso-RFC runs + + # Retrieve paths from the preloaded index + partial_list_ = path_index[fid].get(i_run, []) + + for elem in partial_list_: + if i_dx==0 or i_dx==1: + if TABU_ZONES_TUPLE[i_dx] in elem: + partial_list.append(elem) + else: + if TABU_ZONES_TUPLE[0] not in elem and TABU_ZONES_TUPLE[1] not in elem: + partial_list.append(elem) + + partial_list_of_lists.append(partial_list) + + list_of_lists.append(partial_list_of_lists) + + return list_of_lists + + def mode_5(path_obj:Path, + exp_df:pd.DataFrame)->List[List[List[List[str]]]]: + r""" + This is the definition of the classification mode 5; + The lists will consist of having type/invariance_mode/function base, + + Then the sublists will contain the pairs algorithm and then contain all the runs. + + + Args: + ------------------- + - path_obj: `Path`: A path object pointing to the root directory of the dataset. + - exp_df: `pd.Dataframe`: A dataframe with information of properties of the run. + """ + + iterable_of_function = list(product(PROBLEMS_MODULES, + INVARIANCE_MODES, + FID_LIMITS)) + unique_sigma = pd.unique(exp_df['Step_Size']) + + list_of_lists:list = [] + + for prob_mod_i,inv_mod_i, fid_i in iterable_of_function: + + # Initialize an empty list to store the iso-sigma_runs + partial_high_list:list = [] + # Iterate over the same seed runs + for i_sigma in unique_sigma: + + sigma_list:list = [] + + partial_df = exp_df[exp_df["Step_Size"]==i_sigma] + list_runs:pd.DataFrame = partial_df['Run'] + # Loop all over the algorithms + for alg_mod_i in ALGORITHMS_MODULES: + + # Initialize the list + partial_list = [] + + # Loop all over the runs + + for i_run in list_runs.to_numpy(dtype=int): + # Build-up the string to search + search_string:str = os.path.join(path_obj.absolute(), + prob_mod_i, + inv_mod_i, + alg_mod_i, + str(f"f{fid_i}"),f"Run_{i_run}").replace("\\",'/') + + # Look for matching substrings + partial_list.append(search_string) + + sigma_list.append(partial_list) + partial_high_list.append(sigma_list) + + # Append the list + list_of_lists.append(partial_high_list) + + return list_of_lists + + + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + root_experiment_repo_: Path = _convert_2_path_object(root_experiment_repo) + + # Check if the root experiment repository exists + if not root_experiment_repo_.exists(): + raise AttributeError("The given path does not exist", + name="root_experiment_repo", + obj=root_experiment_repo) + + #for ii, ff in enumerate(list_generator): + #rint(ii, ff) + # a = 1 + + if mode==1: + return mode_1(root_experiment_repo_, + exp_file_df) + elif mode==2: + return mode_2(root_experiment_repo_, + exp_file_df) + elif mode==3: + return mode_3(root_experiment_repo_, + exp_file_df) + elif mode==4: + return mode_4(root_experiment_repo_, + exp_file_df) + + # elif mode==5: + # return mode_5(root_experiment_repo_, + # exp_file_df) + + + +def plot_mode_1(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_1"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=1) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_xscale('log') + ax1.set_ylabel("Best so far") + #ax1.set_yscale('log') + for idx_curve,iFile in enumerate(sublist): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + tabu_definition:str = data_dict['tabu_definition'] + run_:int = data_dict['run'] + + # Get the sigma/step size + cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] + + # Get the c factor from the de Nobel (2024), which is listed in the experiment file + c_factor:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"RFC"].to_numpy().ravel()[0] + + if idx_curve == 0: + # Match the run with the seed + seed = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Seed"].to_numpy().ravel()[0] + + if tabu_definition is None: + label_str:str = "No Tabu Zone" + else: + label_str:str = tabu_definition + + # plot the curves + ax1.plot(evaluations_dataset.loc[:,["evaluations"]].to_numpy(), + evaluations_dataset.loc[:,["raw_y_best"]].to_numpy(), + label=label_str) + + + ax1.set_title(f"Function: {func_name}, Seed: {str(seed)}, Run: {run_}, c: {c_factor}") + ax1.legend(loc='best') + + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + cur_alg, + f"f{func_id}", + f"Run_{run_}")) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + +def plot_mode_2(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_2"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=2) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_xscale('log') + ax1.set_ylabel("Best so far") + #ax1.set_yscale('log') + for idx_curve,iFile in enumerate(sublist): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + tabu_definition:str = data_dict['tabu_definition'] + run_:int = data_dict['run'] + + + + # Get the c factor from the de Nobel (2024), which is listed in the experiment file + c_factor:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"RFC"].to_numpy().ravel()[0] + + + if tabu_definition is None: + label_str:str = "No Tabu Zone" + else: + label_str:str = tabu_definition + + # plot the curves + ax1.plot(evaluations_dataset.loc[:,["evaluations"]].to_numpy(), + evaluations_dataset.loc[:,["raw_y_best"]].to_numpy()) + + + ax1.set_title(f"Function: {func_name}, c: {c_factor}") + + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + cur_alg, + label_str, + f"f{func_id}", + str(c_factor))) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + +def plot_mode_3(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_3"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=3) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_xscale('log') + ax1.set_ylabel("Best so far") + #ax1.set_yscale('log') + for idx_curve,iFile in enumerate(sublist): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + tabu_definition:str = data_dict['tabu_definition'] + run_:int = data_dict['run'] + + + # Get the c factor from the de Nobel (2024), which is listed in the experiment file + c_factor:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"RFC"].to_numpy().ravel()[0] + + # Get the actual seed + seed:int = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Seed"].to_numpy().ravel()[0] + + + if tabu_definition is None: + label_str:str = "No Tabu Zone" + else: + label_str:str = tabu_definition + + # plot the curves + ax1.plot(evaluations_dataset.loc[:,["evaluations"]].to_numpy(), + evaluations_dataset.loc[:,["raw_y_best"]].to_numpy(), + label= f"$c=${c_factor}") + + + ax1.set_title(f"Function: {func_name}, seed: {seed}") + ax1.legend(loc='best') + + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + cur_alg, + label_str, + f"f{func_id}", + str(seed))) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + +def plot_mode_4(exp_file: Union[str, Path], + root_experiment_repo: Union[str, Path], + save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_4"))->None: + + # Import matplotlib + import matplotlib.pyplot as plt + + + # Get the list of arrays with all the possible plots + list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, + root_experiment_repo=root_experiment_repo, + mode=4) + + # Load again the experiment file + exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + + # Start looping all over the list + for idx_plot,sublist in enumerate(list_): + + # Initialize a plot object + fig1, ax1 = plt.subplots(layout='constrained') + + ax1.set_xlabel("Evaluations") + ax1.set_ylabel("Best so far") + + ax1.set_xscale('log') + + for idx_RFC,i_list_RFC in enumerate(sublist): + + # Compressed Evaluations df + df_compressed:Union[None,pd.DataFrame] = None # Initialize + + for idx_file, iFile in enumerate(i_list_RFC): + + # Break down the path to extract information regarding the function + data_dict:dict = _return_identifiers_from_file_path(iFile) + # Load the data + df_data:dict = load_particular_data(iFile) + + # Extract the evaluations dataset + evaluations_dataset:pd.DataFrame = df_data['evaluations'] + + # Get the function type, invariance mode, function id and run + func_type:str = data_dict['func_type'] + inv_mode:str = data_dict['inv_mode'] + cur_alg:str = data_dict['cur_alg'] + func_id:int = data_dict['func_id'] + func_name:str = data_dict['func_name'] + tabu_definition:str = data_dict['tabu_definition'] + run_:int = data_dict['run'] + + if tabu_definition is None: + label_str:str = "No Tabu Zone" + else: + label_str:str = tabu_definition + + if idx_file == 0: + df_compressed = evaluations_dataset.copy() + # Reassign the runs column + df_compressed["run"] = df_compressed["run"].replace(1, run_) + else: + # Change the run number + evaluations_dataset["run"] = evaluations_dataset["run"].replace(1, run_) + + # append to last + df_compressed = pd.concat([df_compressed, evaluations_dataset],axis=0) + + + + # if idx_curve == 0: + # # Match the run with the step size + # cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] + # step_size_index:int = exp_file_df[exp_file_df["Run"]==run_].index.to_numpy().ravel()[0] + 1 + + # Get the current sigma + cur_RFC:float = exp_file_df.loc[idx_RFC,"RFC"] + + # Get a consolidated average and standard deviation + mean_df, std_df, count_df = (df_compressed.groupby(['evaluations']).mean(), + df_compressed.groupby(['evaluations']).std(), + df_compressed.groupby(['evaluations']).count()) + + r""" + mean_arr,std_arr, count_arr = (data_array[idx].groupby(['evaluations']).mean(), + data_array[idx].groupby(['evaluations']).std(), + data_array[idx].groupby(['evaluations']).count()) + + ax_ptr.plot(mean_arr.index.to_numpy(),-1*mean_arr['Objective'],label=f"{DIMENSIONS[idx]}D") + up_bound = -1*mean_arr['Objective'].to_numpy() + 1.96*std_arr['Objective'].to_numpy()/np.sqrt(count_arr['Objective'].to_numpy()) + lo_bound = -1*mean_arr['Objective'].to_numpy() - 1.96*std_arr['Objective'].to_numpy()/np.sqrt(count_arr['Objective'].to_numpy()) + ax_ptr.fill_between(x=mean_arr.index.to_numpy(),y1 = up_bound, y2 = lo_bound, alpha=0.2) + + """ + + # plot the curves + ax1.plot(mean_df.index.to_numpy(), + mean_df['raw_y_best'].to_numpy(), + label=f"$c=${cur_RFC}") + + up_bound = mean_df['raw_y_best'].to_numpy() + 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + lo_bound = mean_df['raw_y_best'].to_numpy() - 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + + ax1.fill_between(x=mean_df.index.to_numpy(), + y1= up_bound, + y2= lo_bound, + alpha=0.2) + + + ax1.set_title(f"Function: {func_name}, Algorithm: {cur_alg}, Tabu Zone: {label_str}") + + + ax1.set_xlim((2e2,1e4)) + ax1.relim() # Recompute the data limits based on the new xlim + + ymin, _ = ax1.get_ylim() + + # if ymin < 0: + # ax1.set_ylim((ymin,0.75*ymin), auto=True) + # else: + # ax1.set_ylim((ymin,1.25*ymin), auto=True) + + ax1.legend(loc="best") + save_path:Path = _convert_2_path_object(os.path.join(save_suffix, + func_type, + inv_mode, + cur_alg, + label_str, + f"f{func_id}", + )) + # Generate a path object + if not save_path.exists(): + save_path.mkdir(exist_ok=False,parents=True) + + + plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + + # Close the figure + plt.close(fig1) + + + + +# def plot_mode_3(exp_file: Union[str, Path], +# root_experiment_repo: Union[str, Path], +# save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_3"))->None: + +# # Import matplotlib +# import matplotlib.pyplot as plt + + +# # Get the list of arrays with all the possible plots +# list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, +# root_experiment_repo=root_experiment_repo, +# mode=3) + +# # Load again the experiment file +# exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + +# # Start looping all over the list +# for idx_plot,sublist in enumerate(list_): + +# # Initialize a plot object +# fig1, ax1 = plt.subplots(layout='constrained') + +# ax1.set_xlabel("Evaluations") +# ax1.set_ylabel("Best so far") + +# #ax1.set_yscale('log') +# for idx_sigma,i_list_sigma in enumerate(sublist): + +# # Compressed Evaluations df +# df_compressed:Union[None,pd.DataFrame] = None # Initialize + +# for idx_file, iFile in enumerate(i_list_sigma): + +# # Break down the path to extract information regarding the function +# data_dict:dict = _return_identifiers_from_file_path(iFile) +# # Load the data +# df_data:dict = load_particular_data(iFile) + +# # Extract the evaluations dataset +# evaluations_dataset:pd.DataFrame = df_data['evaluations'] + +# # Get the function type, invariance mode, function id and run +# func_type:str = data_dict['func_type'] +# inv_mode:str = data_dict['inv_mode'] +# cur_alg:str = data_dict['cur_alg'] +# func_id:int = data_dict['func_id'] +# func_name:str = data_dict['func_name'] +# run_:int = data_dict['run'] + +# if idx_file == 0: +# df_compressed = evaluations_dataset.copy() +# # Reassign the runs column +# df_compressed["run"] = df_compressed["run"].replace(1, run_) +# else: +# # Change the run number +# evaluations_dataset["run"] = evaluations_dataset["run"].replace(1, run_) + +# # append to last +# df_compressed = pd.concat([df_compressed, evaluations_dataset],axis=0) + + + +# # if idx_curve == 0: +# # # Match the run with the step size +# # cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] +# # step_size_index:int = exp_file_df[exp_file_df["Run"]==run_].index.to_numpy().ravel()[0] + 1 + +# # Get the current sigma +# cur_sigma:float = exp_file_df.loc[idx_sigma,"Step_Size"] + +# # Get a consolidated average and standard deviation +# mean_df, std_df, count_df = (df_compressed.groupby(['evaluations']).mean(), +# df_compressed.groupby(['evaluations']).std(), +# df_compressed.groupby(['evaluations']).count()) + +# r""" +# mean_arr,std_arr, count_arr = (data_array[idx].groupby(['evaluations']).mean(), +# data_array[idx].groupby(['evaluations']).std(), +# data_array[idx].groupby(['evaluations']).count()) + +# ax_ptr.plot(mean_arr.index.to_numpy(),-1*mean_arr['Objective'],label=f"{DIMENSIONS[idx]}D") +# up_bound = -1*mean_arr['Objective'].to_numpy() + 1.96*std_arr['Objective'].to_numpy()/np.sqrt(count_arr['Objective'].to_numpy()) +# lo_bound = -1*mean_arr['Objective'].to_numpy() - 1.96*std_arr['Objective'].to_numpy()/np.sqrt(count_arr['Objective'].to_numpy()) +# ax_ptr.fill_between(x=mean_arr.index.to_numpy(),y1 = up_bound, y2 = lo_bound, alpha=0.2) + +# """ + +# # plot the curves +# ax1.plot(mean_df.index.to_numpy(), +# mean_df['raw_y_best'].to_numpy(), +# label=f"$\sigma_0=${cur_sigma}") + +# up_bound = mean_df['raw_y_best'].to_numpy() + 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) +# lo_bound = mean_df['raw_y_best'].to_numpy() - 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + +# ax1.fill_between(x=mean_df.index.to_numpy(), +# y1= up_bound, +# y2= lo_bound, +# alpha=0.2) + + +# ax1.set_title(f"Function: {func_name}, Algorithm: {cur_alg}") + +# ax1.legend(loc="best") +# save_path:Path = _convert_2_path_object(os.path.join(save_suffix, +# func_type, +# inv_mode, +# cur_alg, +# f"f{func_id}", +# )) +# # Generate a path object +# if not save_path.exists(): +# save_path.mkdir(exist_ok=False,parents=True) + + +# plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + +# # Close the figure +# plt.close(fig1) + +# def plot_mode_4(exp_file: Union[str, Path], +# root_experiment_repo: Union[str, Path], +# save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_4"))->None: + +# # Import matplotlib +# import matplotlib.pyplot as plt + + +# # Get the list of arrays with all the possible plots +# list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, +# root_experiment_repo=root_experiment_repo, +# mode=4) + +# # Load again the experiment file +# exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + +# # Start looping all over the list +# for idx_plot,sublist in enumerate(list_): + +# # Initialize a plot object +# fig1, ax1 = plt.subplots(layout='constrained') + +# ax1.set_xlabel("Evaluations") +# ax1.set_ylabel("Best so far") + +# actual_seed = None + +# for idx_file, iFile in enumerate(sublist): + +# # Break down the path to extract information regarding the function +# data_dict:dict = _return_identifiers_from_file_path(iFile) +# # Load the data +# df_data:dict = load_particular_data(iFile) + +# # Extract the evaluations dataset +# evaluations_dataset:pd.DataFrame = df_data['evaluations'] + +# # Get the function type, invariance mode, function id and run +# func_type:str = data_dict['func_type'] +# inv_mode:str = data_dict['inv_mode'] +# cur_alg:str = data_dict['cur_alg'] +# func_id:int = data_dict['func_id'] +# func_name:str = data_dict['func_name'] +# run_:int = data_dict['run'] + + +# if idx_file == 0: +# # Match the run with the step size +# cur_step_size:float = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Step_Size"].to_numpy().ravel()[0] +# step_size_index:int = exp_file_df[exp_file_df["Run"]==run_].index.to_numpy().ravel()[0] + 1 +# actual_seed = exp_file_df[exp_file_df["Run"]==run_].loc[:,"Seed"].to_numpy().ravel()[0] + +# # plot the curves +# ax1.plot(evaluations_dataset.loc[:,["evaluations"]].to_numpy(), +# evaluations_dataset.loc[:,["raw_y_best"]].to_numpy(), +# label=cur_alg) + + +# ax1.set_title(f"Function: {func_name}, Seed:{actual_seed}, $\sigma_0=${cur_step_size}") + +# ax1.legend(loc="best") +# save_path:Path = _convert_2_path_object(os.path.join(save_suffix, +# func_type, +# inv_mode, +# f"f{func_id}", +# f"Run_{run_}" +# )) +# # Generate a path object +# if not save_path.exists(): +# save_path.mkdir(exist_ok=False,parents=True) + + +# plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + +# # Close the figure +# plt.close(fig1) + +# def plot_mode_5(exp_file: Union[str, Path], +# root_experiment_repo: Union[str, Path], +# save_suffix:str = os.path.join(os.getcwd(),"plots","Mode_5"))->None: + +# # Import matplotlib +# import matplotlib.pyplot as plt + + +# # Get the list of arrays with all the possible plots +# list_:List[List[str]] = _classify_run_folders(exp_file=exp_file, +# root_experiment_repo=root_experiment_repo, +# mode=5) + +# # Load again the experiment file +# exp_file_df:pd.DataFrame = load_experiment_file(exp_file) + +# all_sigmas = pd.unique(exp_file_df['Step_Size']) + +# # Start looping all over the list +# for idx_plot,sublist in enumerate(list_): + + +# #ax1.set_yscale('log') +# for idx_sigma,i_list_sigma in enumerate(sublist): + +# # Initialize a plot object +# fig1, ax1 = plt.subplots(layout='constrained') + +# ax1.set_xlabel("Evaluations") +# ax1.set_ylabel("Best so far") + +# for idx_alg, i_list_alg in enumerate(i_list_sigma): + +# # Compressed Evaluations df +# df_compressed:Union[None,pd.DataFrame] = None # Initialize + +# for idx_file, iFile in enumerate(i_list_alg): + +# # Break down the path to extract information regarding the function +# data_dict:dict = _return_identifiers_from_file_path(iFile) +# # Load the data +# df_data:dict = load_particular_data(iFile) + +# # Extract the evaluations dataset +# evaluations_dataset:pd.DataFrame = df_data['evaluations'] + +# # Get the function type, invariance mode, function id and run +# func_type:str = data_dict['func_type'] +# inv_mode:str = data_dict['inv_mode'] +# cur_alg:str = data_dict['cur_alg'] +# func_id:int = data_dict['func_id'] +# func_name:str = data_dict['func_name'] +# run_:int = data_dict['run'] + +# if idx_file == 0: +# df_compressed = evaluations_dataset.copy() +# # Reassign the runs column +# df_compressed["run"] = df_compressed["run"].replace(1, run_) +# else: +# # Change the run number +# evaluations_dataset["run"] = evaluations_dataset["run"].replace(1, run_) + +# # append to last +# df_compressed = pd.concat([df_compressed, evaluations_dataset],axis=0) + +# # Get the current sigma +# cur_sigma:float = all_sigmas[idx_sigma] + +# # Get a consolidated average and standard deviation +# mean_df, std_df, count_df = (df_compressed.groupby(['evaluations']).mean(), +# df_compressed.groupby(['evaluations']).std(), +# df_compressed.groupby(['evaluations']).count()) + +# # plot the curves +# ax1.plot(mean_df.index.to_numpy(), +# mean_df['raw_y_best'].to_numpy(), +# label=cur_alg) + +# up_bound = mean_df['raw_y_best'].to_numpy() + 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) +# lo_bound = mean_df['raw_y_best'].to_numpy() - 1.96*std_df['raw_y_best'].to_numpy()/np.sqrt(count_df['raw_y_best'].to_numpy()) + +# ax1.fill_between(x=mean_df.index.to_numpy(), +# y1= up_bound, +# y2= lo_bound, +# alpha=0.2) + + +# ax1.set_title(f"Function: {func_name}, $\sigma_0=$ {cur_sigma}") + +# ax1.legend(loc="best") +# save_path:Path = _convert_2_path_object(os.path.join(save_suffix, +# func_type, +# inv_mode, +# f"f{func_id}", +# f"Step_size_{idx_sigma+1}" +# )) +# # Generate a path object +# if not save_path.exists(): +# save_path.mkdir(exist_ok=False,parents=True) + + +# plt.savefig(os.path.join(save_path.absolute(),"convergence.pdf"),format="pdf") + +# # Close the figure +# plt.close(fig1) +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/injection/f10/Run_1" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/rejection_sampling/f10/Run_4" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/pure/f10/Run_1" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/pure/f8/Run_6" +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOB/average/rejection_sampling/f8/Run_6" + +#trial_path:str = "C:/Users/iolar/Documents/Modular_Problems_IOH/ModularExperiments/NonBBOBWithSubspace/average/rejection_sampling/f6/Run_6" +#trial_path:str = "C:/Users/iolar/Downloads/Loaded_ModularExperiments/NonBBOB/average/pure/f1/Run_100" +#trial_path:str = "C:/Users/iolar/Downloads/Loaded_ModularExperiments/NonBBOB/average/injectioN/f7/Run_1" + +#list_ = load_particular_data(trial_path) +#print(list_['fit']) +#print(list_['evaluations']) + +#plot_run_iterations_on_contour(trial_path,list_) + +#resulting_list:list = classify_run_folders( +# exp_file="C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_1.csv", +## root_experiment_repo="C:/Users/iolar/Downloads/Loaded_ModularExperiments", + # mode=1) + +#plot_mode_5("C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_1.csv", + # "C:/Users/iolar/Downloads/Loaded_ModularExperiments") + + +# Insert this path +trial_path = Path("C:/Users/iolar/Downloads/Other_Experiments") + +lllist = _classify_run_folders("C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_2.csv", + trial_path, + mode=4) + +print(lllist) + +#plot_mode_1("C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_2.csv", +# trial_path, +# save_suffix="C:/Users/iolar/Downloads/Other_Experiments_1/Plots_Mode_1") + +# plot_mode_2("C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_2.csv", +# trial_path, +# save_suffix="C:/Users/iolar/Downloads/Other_Experiments_1/Plots_Mode_2") + + +# plot_mode_3("C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_2.csv", +# trial_path, +# save_suffix="C:/Users/iolar/Downloads/Other_Experiments_1/Plots_Mode_3") + + +plot_mode_4("C:/Users/iolar/Documents/Modular_Problems_IOH/Dataset_2.csv", + trial_path, + save_suffix="C:/Users/iolar/Downloads/Other_Experiments_1/Plots_Mode_4") \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/IOH_parser.py b/mylib/lib_BO_torch_repo/GlobalUtilities/IOH_parser.py new file mode 100644 index 0000000..3a8fce4 --- /dev/null +++ b/mylib/lib_BO_torch_repo/GlobalUtilities/IOH_parser.py @@ -0,0 +1,231 @@ +import os +import sys + +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +import json + +from typing import Union, List, Tuple, Optional + +class IOH_Parser: + + + def __init__(self, filepath:str, external_typing:Optional[dict]=None): + + r""" + This is the class initializer for the IOH_Parser class. + This takes a filepath pointing to a IOH file. + + Args: + --------------- + filepath: `str`: A pointer to the IOH file with the information of a set of runs of + a defined algorithm over an optimization problem. + external_typing: `dict`: A dictionary with the column name as the key and the key + maps to the datatype to which the variable should be transformed. + + """ + + # Load the file to parse the information + try: + with open(filepath, 'r') as file: + data = json.load(file) + + + except FileNotFoundError as e: + # Raise this error in case the file is not found + print(e.args) + + except json.JSONDecodeError as e: + print(e.args) + + #except Exception as e: + # raise(e.args) + + # Get the current directory + curdir = os.path.dirname(filepath) + + self.__json_file:str = filepath + # Fill the parser + self.__algorithm:Algor = Algor(data['algorithm']) + self.__version = data['version'] + self.__function_id = data['function_id'] + self.__function_name = data['function_name'] + self.__maximization = data['maximization'] + self.__attributes = data["attributes"] + self.__scenarios:list = [Opt_Scenario(a,curdir,replacing_dict=external_typing) for idx,a in enumerate(data["scenarios"])] + + + def reload(self): + # Load the file to parse the information + try: + with open(self.json_file, 'r') as file: + data = json.load(file) + + + except FileNotFoundError as e: + # Raise this error in case the file is not found + raise(e.args) + + # Get the current directory + curdir = os.path.dirname(self.json_file) + + # Fill the parser + self.__algorithm:Algor = Algor(data['algorithm']) + self.__version = data['version'] + self.__function_id = data['function_id'] + self.__function_name = data['function_name'] + self.__maximization = data['maximization'] + self.__attributes = data["attributes"] + self.__scenarios:list = [Opt_Scenario(a,curdir) for idx,a in enumerate(data["scenarios"])] + + + @property + def json_file(self)-> str: + return self.__json_file + + @property + def version(self)-> str: + return self.__version + + @property + def function_id(self)-> int: + return self.__function_id + + @property + def function_name(self)-> int: + return self.__function_name + + @property + def maximization(self)-> bool: + return self.__maximization + + @property + def attributes(self)-> dict: + return self.__attributes + + @property + def algorithm_name(self)->str: + return self.__algorithm.name + + @property + def algorithm_info(self)->str: + return self.__algorithm.info + + @property + def number_of_instances(self)->int: + return len(self.__scenarios) + + + def return_complete_table_per_instance(self,instance_id:int): + if instance_id < 0 or instance_id > self.number_of_instances -1: + raise("The position is out of bounds!") + + return self.__scenarios[instance_id].data + + def get_the_best_score_per_instance(self,instance_id:int): + if instance_id < 0 or instance_id > self.number_of_instances -1: + raise(ValueError()) + + if self.maximization: + return self.__scenarios[instance_id].data.groupby("run").max() + else: + return self.__scenarios[instance_id].data.groupby("run").min() + + + +class Algor: + def __init__(self,algorithm_dict:dict): + self.__name = algorithm_dict['name'] + self.__info = algorithm_dict['info'] + + + @property + def name(self)->str: + return self.__name + + @property + def info(self)->str: + return self.__info + + +class Opt_Scenario: + def __init__(self,scenarios_dict:dict, directory:str, replacing_dict:Optional[dict]=None) -> None: + self.__dimensions:int = scenarios_dict['dimension'] + self.__path:str = os.path.join(directory, scenarios_dict['path']) + self.__num_runs:int = len(scenarios_dict['runs']) + self.__data:pd.DataFrame = self.__load_dat_file_with_iterations() + + + + @property + def dimensions(self)->int: + return self.__dimensions + + @property + def path(self)->str: + return self.__path + + @property + def data(self)->pd.DataFrame: + return self.__data + + def best_per_run(self)-> pd.DataFrame: + return self.__best_per_run + + + def __load_dat_file_with_iterations(self, replacing_dict:Optional[dict]=None)->pd.DataFrame: + + "Use the default pandas import" + try: + df:pd.DataFrame = pd.read_csv(self.path, delimiter=" ") + except FileNotFoundError as e: + raise("The file was not found", e.args) + except Exception as e: + raise("Cannot trace the source of error", e.args) + + if df.dtypes.iloc[0] == pd.StringDtype: + boolean_elims = df.evaluations.str.contains('evaluations') == False + else: + boolean_elims = np.ones(df.evaluations.size,dtype=bool) + + + init_run:int = 1 + list_:np.ndarray = np.zeros(boolean_elims.size, dtype=int) + for a,b in enumerate(boolean_elims): + if b == True: + list_[a] = init_run + else: + init_run += 1 + list_[a] = init_run + + df = df.assign(run=list_) + + cols = list(df.columns) + cols = [cols[-1] ] + cols[0:-1] + + # Eliminate the repeated headers + df = df[boolean_elims] + df = df[cols] + + # Convert all the instances to floats + for idx,col in enumerate(df.columns): + if col == 'raw_y' or (col.startswith('x') and len(col.split('x'))==2): + df[col] = df[col].astype(float) + elif col == 'raw_y_best': + df[col] = df[col].astype(float) + elif col== 'evaluations': + df[col] = df[col].astype(int) + elif col== 'cur_sea' or col=="cur_intrusion": + df[col] = df[col].astype(float) + elif replacing_dict is not None: + if col in [*replacing_dict.keys()]: + # Replace the datatype in this case + df[col] = df[col].astype(replacing_dict[col]) + + return df + + + + \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/__init__.py b/mylib/lib_BO_torch_repo/GlobalUtilities/__init__.py new file mode 100644 index 0000000..6253dda --- /dev/null +++ b/mylib/lib_BO_torch_repo/GlobalUtilities/__init__.py @@ -0,0 +1,3 @@ +from .parsing_one_dimensional_intrinsic import parse_args, _alg_mapper, _suite_mapper +from .parsing_one_dimensional_Gallagher import parse_args_gallagher +from .IOH_parser import IOH_Parser \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/IOH_parser.cpython-310.pyc b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/IOH_parser.cpython-310.pyc new file mode 100644 index 0000000..2b456e4 Binary files /dev/null and b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/IOH_parser.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..2d7bb7d Binary files /dev/null and b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/__init__.cpython-311.pyc b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..b4c2aba Binary files /dev/null and b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/__init__.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_Gallagher.cpython-310.pyc b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_Gallagher.cpython-310.pyc new file mode 100644 index 0000000..2953688 Binary files /dev/null and b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_Gallagher.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_intrinsic.cpython-310.pyc b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_intrinsic.cpython-310.pyc new file mode 100644 index 0000000..2e69493 Binary files /dev/null and b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_intrinsic.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_intrinsic.cpython-311.pyc b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_intrinsic.cpython-311.pyc new file mode 100644 index 0000000..1a7c15f Binary files /dev/null and b/mylib/lib_BO_torch_repo/GlobalUtilities/__pycache__/parsing_one_dimensional_intrinsic.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/parsing_one_dimensional_Gallagher.py b/mylib/lib_BO_torch_repo/GlobalUtilities/parsing_one_dimensional_Gallagher.py new file mode 100644 index 0000000..70e785a --- /dev/null +++ b/mylib/lib_BO_torch_repo/GlobalUtilities/parsing_one_dimensional_Gallagher.py @@ -0,0 +1,190 @@ +import os +import sys +from argparse import ArgumentParser, Namespace, ArgumentTypeError, ArgumentError +from typing import List, NamedTuple, Tuple, Union +from Algorithms import Pure_CMA_ES, RejectionSamplingCMA_ES, SpecialInjectionCMA_ES +from IOH_Wrappers import ModularNonBBOBProblem, ModularNonBBOBProblemWithSubspace + +def check_positive(value): + ivalue = int(value) + if ivalue <= 0: + raise ArgumentTypeError("%s is an invalid positive int value" % value) + return ivalue + +def check_positive2(value): + r""" + This is to check the value is positive and not zero + """ + # Convert the value to a float + try: + value = float(value) + except ValueError: + raise ArgumentTypeError("%s is an invalid positive float value" % value) + + if value <= 0.00: + raise ArgumentTypeError("%s is an invalid positive float value" % value) + return value + +def check_boolean(value:Union[bool,int,str]): + if isinstance(value,int): + ivalue = bool(value) + elif isinstance(value,bool): + ivalue = value + elif isinstance(value,str): + if value.strip() == "0": + ivalue = False + elif value.strip() == "1": + ivalue = True + elif value.strip().lower() == "true": + ivalue = True + elif value.strip().lower() == "false": + ivalue = False + else: + raise ArgumentError() + else: + raise ArgumentTypeError("%s is an invalid value" % value) + + return ivalue + + + + +def parse_args_gallagher(args)-> Namespace: + r""" + Define a CLI parser and parse command line arguments + + Args: + --------- + args: command line arguments + + Returns: + --------- + Namespace: parsed command line arguments + + """ + + parser = ArgumentParser( + description=r"""Run modified BBOB problems with CMA-ES algorithms + to compare the performance of these when optimizing + the problems with permutation invariant landscapes""", + add_help=True, + ) + parser.add_argument("--run", + type=check_positive, + default=1, + help="Run Number") + parser.add_argument("--seed", + type=check_positive, + default=43, + help="Random seed") + parser.add_argument("--budget", + type=check_positive, + default=5000, + help="Budget") + parser.add_argument("--algorithm", + type=str, + default="pure", + choices=["pure", "rejection_sampling", "injection"], + help="CMA-ES Algorithm to run") + # parser.add_argument("--fid", + # type=int, + # default=11, + # choices=[1,2,3,4,5,6,7,8,9,10,11,12,13,14], + # help="Function ID") + parser.add_argument("--instance", + type=check_positive, + default=1, + help="Instance of Gallagher Function") + parser.add_argument("--num_peaks", + type=check_positive, + default=1, + help="Number of Peaks to check") + parser.add_argument("--repetitions", + type=int, + default=2, + help="Number of repetitions") + # parser.add_argument("--invariance_mode", + # type=str, + # default="average", + # choices=["average","maximum"], + # help="Invariance mode") + # parser.add_argument("--suite", + # type=str, + # default="NonBBOB", + # choices=["NonBBOB","NonBBOBWithSubspace"], + # help="Suite") + parser.add_argument("--step_size", + type=float, + default=2.5, + help="Step size for CMA-ES initial parameter") + parser.add_argument("--restarts", + type=check_positive, + default=1, + help="Number of restarts for CMA-ES") + parser.add_argument("--root_folder", + type=str, + default=os.getcwd(), + help="Root folder to store data") + parser.add_argument("--cma_active", + type=check_boolean, + default=True, + help="Use Active CMA-ES or not") + parser.add_argument("--max_loop", + type=int, + default=1000, + help="Maximum number of loops to seek for rejection sampling") + parser.add_argument("--verbose", + "-v", + type=check_boolean, + default=False, + help="Verbose mode") + + parser.add_argument("--shrinkage_factor", + "-sf", + type=float, + default=0.8, + help="Shrinkage factor for definition of Rejection close to Tabu Regions") + parser.add_argument("--rejection_factor_c", + '-rfc', + type=check_positive2, + default=2, + help="A parameter to define the rejection region. ", + ) + parser.add_argument("--tabu_active", + "-ta", + type=check_boolean, + default=False, + help="Use Tabu Search or not") + + parser.add_argument("--n_evals_hill_valley", + '-nehv', + type=check_positive, + default=5, + help="Number of evaluations to make for the hill-valley test. ", + ) + + parser.add_argument("--consider_intrinsic_dimension", + '-cid', + type=check_boolean, + default=True, + help="A handler to consider intrinsic Dimension when processing Tabu Regions. ", + ) + + pars = parser.parse_args(args) + + + return pars + +# _suite_mapper = { +# "NonBBOB": ModularNonBBOBProblem, +# "NonBBOBWithSubspace": ModularNonBBOBProblemWithSubspace +# } + +# _alg_mapper = { +# "pure": Pure_CMA_ES, +# "rejection-sampling": RejectionSamplingCMA_ES, +# "injection": SpecialInjectionCMA_ES +# } + + + diff --git a/mylib/lib_BO_torch_repo/GlobalUtilities/parsing_one_dimensional_intrinsic.py b/mylib/lib_BO_torch_repo/GlobalUtilities/parsing_one_dimensional_intrinsic.py new file mode 100644 index 0000000..63a2b98 --- /dev/null +++ b/mylib/lib_BO_torch_repo/GlobalUtilities/parsing_one_dimensional_intrinsic.py @@ -0,0 +1,182 @@ +import os +import sys +from argparse import ArgumentParser, Namespace, ArgumentTypeError, ArgumentError +from typing import List, NamedTuple, Tuple, Union +from Algorithms import Pure_CMA_ES, RejectionSamplingCMA_ES, SpecialInjectionCMA_ES +from IOH_Wrappers import ModularNonBBOBProblem, ModularNonBBOBProblemWithSubspace + +def check_positive(value): + ivalue = int(value) + if ivalue <= 0: + raise ArgumentTypeError("%s is an invalid positive int value" % value) + return ivalue + +def check_positive2(value): + r""" + This is to check the value is positive and not zero + """ + # Convert the value to a float + try: + value = float(value) + except ValueError: + raise ArgumentTypeError("%s is an invalid positive float value" % value) + + if value <= 0.00: + raise ArgumentTypeError("%s is an invalid positive float value" % value) + return value + +def check_boolean(value:Union[bool,int,str]): + if isinstance(value,int): + ivalue = bool(value) + elif isinstance(value,bool): + ivalue = value + elif isinstance(value,str): + if value.strip() == "0": + ivalue = False + elif value.strip() == "1": + ivalue = True + elif value.strip().lower() == "true": + ivalue = True + elif value.strip().lower() == "false": + ivalue = False + else: + raise ArgumentError() + else: + raise ArgumentTypeError("%s is an invalid value" % value) + + return ivalue + + + + +def parse_args(args)-> Namespace: + r""" + Define a CLI parser and parse command line arguments + + Args: + --------- + args: command line arguments + + Returns: + --------- + Namespace: parsed command line arguments + + """ + + parser = ArgumentParser( + description=r"""Run modified BBOB problems with CMA-ES algorithms + to compare the performance of these when optimizing + the problems with permutation invariant landscapes""", + add_help=True, + ) + parser.add_argument("--run", + type=check_positive, + default=1, + help="Run Number") + parser.add_argument("--seed", + type=check_positive, + default=43, + help="Random seed") + parser.add_argument("--budget", + type=check_positive, + default=5000, + help="Budget") + parser.add_argument("--algorithm", + type=str, + default="pure", + choices=["pure", "rejection_sampling", "injection"], + help="CMA-ES Algorithm to run") + parser.add_argument("--fid", + type=int, + default=11, + choices=[1,2,3,4,5,6,7,8,9,10,11,12,13,14], + help="Function ID") + parser.add_argument("--repetitions", + type=int, + default=2, + help="Number of repetitions") + parser.add_argument("--invariance_mode", + type=str, + default="average", + choices=["average","maximum"], + help="Invariance mode") + parser.add_argument("--suite", + type=str, + default="NonBBOB", + choices=["NonBBOB","NonBBOBWithSubspace"], + help="Suite") + parser.add_argument("--step_size", + type=float, + default=2.5, + help="Step size for CMA-ES initial parameter") + parser.add_argument("--restarts", + type=check_positive, + default=1, + help="Number of restarts for CMA-ES") + parser.add_argument("--root_folder", + type=str, + default=os.getcwd(), + help="Root folder to store data") + parser.add_argument("--cma_active", + type=check_boolean, + default=True, + help="Use Active CMA-ES or not") + parser.add_argument("--max_loop", + type=int, + default=1000, + help="Maximum number of loops to seek for rejection sampling") + parser.add_argument("--verbose", + "-v", + type=check_boolean, + default=False, + help="Verbose mode") + + parser.add_argument("--shrinkage_factor", + "-sf", + type=float, + default=0.8, + help="Shrinkage factor for definition of Rejection close to Tabu Regions") + parser.add_argument("--rejection_factor_c", + '-rfc', + type=check_positive2, + default=2, + help="A parameter to define the rejection region. ", + ) + parser.add_argument("--tabu_active", + "-ta", + type=check_boolean, + default=False, + help="Use Tabu Search or not") + + parser.add_argument("--n_evals_hill_valley", + '-nehv', + type=check_positive, + default=5, + help="Number of evaluations to make for the hill-valley test. ", + ) + + parser.add_argument("--consider_intrinsic_dimension", + '-cid', + type=check_boolean, + default=True, + help="A handler to consider intrinsic Dimension when processing Tabu Regions. ", + ) + + pars = parser.parse_args(args) + + + return pars + +_suite_mapper = { + "NonBBOB": ModularNonBBOBProblem, + "NonBBOBWithSubspace": ModularNonBBOBProblemWithSubspace +} + +_alg_mapper = { + "pure": Pure_CMA_ES, + "rejection-sampling": RejectionSamplingCMA_ES, + "injection": SpecialInjectionCMA_ES +} + + + diff --git a/mylib/lib_BO_torch_repo/HierarchicalSorting/Abstract_Sorting.py b/mylib/lib_BO_torch_repo/HierarchicalSorting/Abstract_Sorting.py new file mode 100644 index 0000000..bde0f96 --- /dev/null +++ b/mylib/lib_BO_torch_repo/HierarchicalSorting/Abstract_Sorting.py @@ -0,0 +1,116 @@ +from typing import List, Union, Tuple, Optional, Callable +import numpy as np +from abc import ABC, abstractmethod + +class Abstract_Sorting: + """ + This class is defined to perform a Single Objective Optimization based by building blocks of + d*r instances of the BBOB functions, where d is the intrinsic dimensionality and + r is the number of repetitions. + + Each target corresponds to the arithmetic mean of multiple instances of the BBOB defined on + some latent dimensions. + """ + @abstractmethod + def __init__(self, intrinsic_dimension:int, + increasing:bool): + r""" + This is the construction of the abstract class. This takes the following parameters: + + Args: + ------------- + - intrinsic_dimension: The intrinsic dimensionality of the module (positive integer) + - increasing: A boolean value to determine if the sorting is increasing or decreasing + """ + + # Set the intrinsic dimensionality and repetitions + # as properties of this class + self.intrinsic_dimension = intrinsic_dimension + self.increasing = increasing + pass + + @abstractmethod + def __call__(self, array_of_arrays:Union[list,np.ndarray], + **kwds)->np.ndarray: + r""" + The `__call__` method of this class just takes an array of arrays and sorts them + and informs the user if the array is sorted or not. This is to use the class as a callable object + when passing to an Evolutionary Algorithm. + + Args: + ------------- + - array_of_arrays: A list of arrays to be sorted + - **kwds: Additional keyword arguments to pass to the function + + ### + As this is a constructor, this method should return a `NumPy` array of arrays as + a setup for other methods + """ + + # Use this as some template to work with the array of arrays as a NumPy array + + if isinstance(array_of_arrays, np.ndarray): + # Make a copy if this is true + return_array = array_of_arrays.copy() + else: + return_array = np.array(array_of_arrays, + dtype=float, + copy=True, + ndmin=2, + subok=False) + return return_array + + + @abstractmethod + def __sort_array(self, + sub_array:Union[List[float],np.ndarray], + **kwargs)->np.ndarray: + pass + + @abstractmethod + def __is_sorted(self, + sub_array:Union[List[float],np.ndarray], + **kwargs)->bool: + pass + + @abstractmethod + def is_feasible(self, + sub_array:Union[List[float],np.ndarray], + f:float, + **kwargs)->bool: + r""" + This is a method to check if the array is feasible. This is to be used in the + framework of CMA-ES to check if the array is feasible or not. This should be + passed via the CMAOptions. + + Args: + ------------ + - sub_array: The array to be checked + - f: The objective function result to be used + """ + + pass + + @property + def intrinsic_dimension(self): + return self.__intrinsic_dimension + + @intrinsic_dimension.setter + def intrinsic_dimension(self,new_intrinsic_dimension:int): + if isinstance(new_intrinsic_dimension,int) and new_intrinsic_dimension > 0: + self.__intrinsic_dimension = new_intrinsic_dimension + else: + raise AttributeError("The intrinsic dimension must be a positive integer") + + @property + def increasing(self): + return self.__increasing + + @increasing.setter + def increasing(self,new_increasing:bool): + if isinstance(new_increasing,bool): + self.__increasing = new_increasing + else: + raise AttributeError("The increasing property must be a boolean value") + + \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/HierarchicalSorting/One_Dimensional_Intrinsic_Sorting.py b/mylib/lib_BO_torch_repo/HierarchicalSorting/One_Dimensional_Intrinsic_Sorting.py new file mode 100644 index 0000000..666a20c --- /dev/null +++ b/mylib/lib_BO_torch_repo/HierarchicalSorting/One_Dimensional_Intrinsic_Sorting.py @@ -0,0 +1,97 @@ +from typing import List, Union, Tuple, Optional +from .Abstract_Sorting import Abstract_Sorting +import numpy as np + +class One_Dimensional_Intrinsic_Sorting(Abstract_Sorting): + r""" + This class is defined to perform a Single Objective Optimization based by building blocks of + 1D instances of the BBOB functions. + + Each target corresponds to the arithmetic mean of multiple instances of the BBOB defined on + some latent dimensions. + """ + def __init__(self, increasing:bool=True): + r""" + This is just an overload of the constructor of the `Abstract_Sorting` class. + This just sets the intrinsic dimensionality to 1. + """ + + # Call the super class initializer + super().__init__(intrinsic_dimension=1, increasing=increasing) + + def __call__(self, array_of_arrays:Union[list,np.ndarray], + **kwds)->Union[np.ndarray,List[bool]]: + + # Call the super class initializer (outputs a two dimensional `NumPy`array) + array_of_arrays_mod:np.ndarray = super().__call__(array_of_arrays, **kwds) + + # Initialize a list of boolean values and a list of arrays with the sorted arrays + is_sorted_list:List[bool] = [] + sorted_arrays:List[Union[List[float],np.ndarray]] = [] + + # Now perform the loop + for _, sub_array in enumerate(array_of_arrays_mod): + # Sort the array and append it to the list + sorted_arrays.append(self.__sort_array(sub_array)) + + # Check if the array is sorted and append it to the list + is_sorted:bool = self.__is_sorted(sub_array) + is_sorted_list.append(is_sorted) + + return sorted_arrays, is_sorted_list + + + def __sort_array(self, + sub_array:Union[List[float],np.ndarray], + **kwargs): + + sorted_array:np.ndarray= np.sort(sub_array, axis=None, kind='quicksort', order=None) + + if self.increasing: + return sorted_array + else: + return sorted_array[::-1] + + def __is_sorted(self, + sub_array:Union[List[float],np.ndarray], + **kwargs)->bool: + + if self.increasing: + return np.all(np.array( + [sub_array[i] <= sub_array[i + 1] for i in range(len(sub_array) - 1)] + ,dtype=bool)) + else: + return np.all(np.array( + [sub_array[i] >= sub_array[i + 1] for i in range(len(sub_array) - 1)] + ,dtype=bool)) + + def is_feasible(self, + sub_array:Union[List[float],np.ndarray], + f:float, + **kwargs)->bool: + r""" + This is a method to check if the array is feasible. This is to be used in the + framework of CMA-ES to check if the array is feasible or not. This should be + passed via the CMAOptions. + + Args: + ------------ + - sub_array: The array to be checked + - f: The objective function result to be used + """ + + # Check the result of the objective function first. This is a replication + # from CMA-ES (Hansen's) specifications + if f in (np.inf, np.nan,None): + return False + else: + # Check if the array is feasible by checking it is sorted + return self.__is_sorted(sub_array,**kwargs) + + def __getstate__(self): + state = self.__dict__.copy() + # Add any non-picklable attributes that need to be excluded here + return state + + def __setstate__(self, state): + self.__dict__.update(state) \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/HierarchicalSorting/__init__.py b/mylib/lib_BO_torch_repo/HierarchicalSorting/__init__.py new file mode 100644 index 0000000..3b812fa --- /dev/null +++ b/mylib/lib_BO_torch_repo/HierarchicalSorting/__init__.py @@ -0,0 +1,2 @@ +from .One_Dimensional_Intrinsic_Sorting import One_Dimensional_Intrinsic_Sorting + diff --git a/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/Abstract_Sorting.cpython-310.pyc b/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/Abstract_Sorting.cpython-310.pyc new file mode 100644 index 0000000..5de5dba Binary files /dev/null and b/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/Abstract_Sorting.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/One_Dimensional_Intrinsic_Sorting.cpython-310.pyc b/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/One_Dimensional_Intrinsic_Sorting.cpython-310.pyc new file mode 100644 index 0000000..8b55076 Binary files /dev/null and b/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/One_Dimensional_Intrinsic_Sorting.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..8fcd0b5 Binary files /dev/null and b/mylib/lib_BO_torch_repo/HierarchicalSorting/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/BBOB_utils.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/BBOB_utils.py new file mode 100644 index 0000000..7807e11 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/BBOB_utils.py @@ -0,0 +1,11 @@ +import numpy as np +import os +import sys + + +__author__ = "Iván Olarte Rodríguez" + +r""" +This is a set of general utilities for the BBOB framework +""" + diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/Generalized_Gallagher_Function.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/Generalized_Gallagher_Function.py new file mode 100644 index 0000000..895f9d5 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/Generalized_Gallagher_Function.py @@ -0,0 +1,209 @@ +import numpy as np +from typing import (List , + Union , + Optional) +from ioh.iohcpp.problem import RealSingleObjective +from ioh.iohcpp import RealBounds, RealSolution + + +def _fpen(x:np.ndarray)->float: + r""" + Compute the value of the fpen function at x by using the BBOB-COCO definition. + + Args: + --------------- + + - x: Input vector of shape (dim,) within the range (-5,5). + """ + # Reshape the input vector + x = x.ravel() + dim = x.size + + # Compute the function value + accumulator:int = 0 + + for i in range(dim): + accumulator += max(0,np.abs(x[i])-5)**2 + + return accumulator + +def _fopt(x:np.ndarray, + xopt:np.ndarray, + norm_type:Union[str,int]=2)->float: + + r""" + This is a function that generates a penalty based on the norm (or distance) + from the optimal point. + """ + dim:int = xopt.size + + if isinstance(norm_type,int): + #return np.linalg.norm(x-xopt,ord=norm_type)/(10*dim**(1/norm_type)) + return np.linalg.norm(x-xopt,ord=norm_type) + else: + return np.linalg.norm(x-xopt,ord=norm_type) + +class GeneralizedGallagherFunction(RealSingleObjective): + + r""" + This is an extension of the Gallagher function by taking the developments from the BBOB-COCO Testbench + (see: https://coco-platform.org/testsuites/bbob/functions/f21.html) as well as the original paper + from Gallagher & Yuan ("A General-Purpose Tunable Landscape Generator", + IEEE TRANSACTIONS ON EVOLUTIONARY COMPUTATION, VOL. 10, NO. 5, OCTOBER 2006). + + To follow the other benchmarks, this class extends the IOH framework: + see: https://iohprofiler.github.io/IOHexperimenter/ + """ + + def __init__(self, + num_peaks:Optional[int]=21, + dim:Optional[int]=2, + instance:Optional[int] =1, + norm_type:Optional[Union[str,int]]=2)->None: + + r""" + The main initializer of the class. + + Args: + ------------- + + - x: Input vector of shape (dim,). + - num_peaks: `int`: Number of Gaussian peaks. + - dim: `int`: Dimensionality of the input space. + - instance: `Optional[int]`; an instance of np.random.RandomState for reproducibility. + - norm_type: `Optional[Union[str,int]]`: The norm to compute a penalty for not being close to the optimum value + """ + + # Input checks + if not (num_peaks >=1 and isinstance(num_peaks,int)): + raise ValueError("The number of peaks should be greater than 1") + + if not (dim >=1 and isinstance(dim,int)): + raise ValueError("The dimension of the problem should be a positive integer") + + #Instantiate a random state given the instance + random_state = np.random.RandomState(instance) + + # Store the norm type + self._norm_type:Union[int,str] = norm_type + + # Generate random peak properties + self._peak_locations:np.ndarray = np.vstack( + ( + random_state.uniform(-3.99, 3.99, (1, dim)), + random_state.uniform(-4.99, 4.99, (num_peaks-1, dim)) + ) + ) + + self._peak_heights:List[float] = [] + + + + self._q_list:List[np.ndarray] = [] + self._r_list:List[np.matrix] = [] + + + # Compute the covariance matrices and compute the spectral eigendecomposition + for i in range(num_peaks): + + if i == 0: + self._peak_heights.append(10) + else: + self._peak_heights.append(1.1+8*(i)/(num_peaks-1)) + + # Generate random matrices + cov = random_state.uniform(-1,1,(dim, dim)) + + cov = np.dot(cov.T,cov) + + # Perform the computation + q, r = np.linalg.eigh(cov) + self._q_list.append(q) + self._r_list.append(r) + + name = "Generalized Gallagher Function with {0} peaks".format(num_peaks) + bounds = RealBounds(dim,-5,5) + optimum = RealSolution( + x=self._peak_locations[0].tolist(), + y=self._compute_value(self._peak_locations[0]) + ) + + # Initialize the superclass + super().__init__( + name=name, + n_variables=dim, + instance=instance, + is_minimization=True, + bounds=bounds, + constraints=[], + optimum= optimum + ) + + def _compute_value(self,x:Union[np.ndarray,List[float]])->float: + r""" + This function computes the value of the function (without the penalties) + + Args: + ----------------- + - x: `Union[np.ndarray,List[float]]`: The vector denoting some point to compute the function. + """ + + # Compute the dimensionality (again!) + dim = self._peak_locations.shape[1] + + if isinstance(x,list): + x = np.asarray(x) + + # Reshape the array + x = x.ravel() + + # Compute the function value + values = [] + + + for i in range(len(self._peak_locations)): + diff = (x - self._peak_locations[i]).reshape(-1,1) + exponent = (-(1/(2*dim)) * (diff.T @ ( self._r_list[i] @ np.diag(np.divide(1,self._q_list[i]) ) @ self._r_list[i].T ) @ diff)).ravel()[0] + value = self._peak_heights[i] * np.exp(exponent) + values.append(value) + + return (10-np.max(values))**2 + + def evaluate(self,x:Union[np.ndarray,List[float]])->float: + r""" + This is an overload of the evaluate function to compute the raw function + """ + + # Compute the dimensionality (again!) + dim = self._peak_locations.shape[1] + + if isinstance(x,list): + x = np.asarray(x) + + # Reshape the array + x = x.ravel() + + return self._compute_value(x) + _fopt(x=x,xopt=self._peak_locations[0],norm_type=self._norm_type) + _fpen(x) + + + def __del__(self, *args, **kwargs): + self.detach_logger() + + + def create(self, id, iid, dim): + raise NotImplementedError + + + + +# Example usage: +#x = #np.array([3.73,0.376]) +# x = np.array([-5,4.1,3,2,1]*10) +# funcc = GeneralizedGallagherFunction(num_peaks=101, +# dim=50, +# instance=5, +# norm_type=2) +# value = funcc(x) + +# print("Function value at x:", value) +# print(f"Function value at optimum: x={funcc.optimum.x}, ", funcc.optimum.y) diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/Generalized_Gallagher_Function_with_repetitions.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/Generalized_Gallagher_Function_with_repetitions.py new file mode 100644 index 0000000..fdac5b9 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/Generalized_Gallagher_Function_with_repetitions.py @@ -0,0 +1,321 @@ +import numpy as np +from typing import (List , + Union , + Optional) +from ioh.iohcpp.problem import RealSingleObjective +from ioh.iohcpp import RealBounds, RealSolution +from itertools import product, permutations + + +def _fpen(x:np.ndarray)->float: + r""" + Compute the value of the fpen function at x by using the BBOB-COCO definition. + + Args: + --------------- + + - x: Input vector of shape (dim,) within the range (-5,5). + """ + # Reshape the input vector + x = x.ravel() + dim = x.size + + # Compute the function value + accumulator:int = 0 + + for i in range(dim): + accumulator += max(0,np.abs(x[i])-5)**2 + + return accumulator + +def _fopt(x:np.ndarray, + xopt:np.ndarray, + n_repetitions:int, + norm_type:Union[str,int]=2)->float: + + r""" + This is a function that generates a penalty based on the norm (or distance) + from the optimal point. + + Args: + ------------------------- + - x: `np.ndarray`: The input array. + - xopt: `np.ndarray`: The location of "an invariant" optimum. + - n_repetitions: `int`: An integer denoting some repetitions (or subgroups) + - norm_type: `Union[str,int]`: The norm type to compute some distance to penalize even more not being close to the optimum. + + Returns: + ------------------------- + - A `float`with the computation of the penalty + """ + + # Get the dimension (hoping the xopt is just a one-dimensional one) + dim:int = xopt.size + + # Set a list to store the computed norm + norm_list:List[float]=[] + + # Get some general indices + gen_indices:np.ndarray = np.arange(start=0,stop=dim,dtype=int) + + # Regroup the indices into groups + group_indices:np.ndarray = gen_indices.reshape( + (n_repetitions,-1) + ) + for k, shuff in enumerate(permutations(range(n_repetitions), + n_repetitions)): + + + shuff_indices = group_indices[np.asarray(shuff),:].ravel() + + shuff_x_opt = xopt[shuff_indices] + + # Compute the norm and append to the list + norm_list.append(np.linalg.norm(x-shuff_x_opt,ord=norm_type)) + + + + return min(norm_list) + + +class GeneralizedGallagherFunctionWithRepetitions(RealSingleObjective): + + r""" + This is an extension of the Gallagher function by taking the developments from the BBOB-COCO Testbench + (see: https://coco-platform.org/testsuites/bbob/functions/f21.html) as well as the original paper + from Gallagher & Yuan ("A General-Purpose Tunable Landscape Generator", + IEEE TRANSACTIONS ON EVOLUTIONARY COMPUTATION, VOL. 10, NO. 5, OCTOBER 2006). + + To follow the other benchmarks, this class extends the IOH framework: + see: https://iohprofiler.github.io/IOHexperimenter/ + """ + + def __init__(self, + dim:int, + n_repetitions:int, + num_peaks:Optional[int]=21, + instance:Optional[int] =1, + norm_type:Optional[Union[str,int]]=2)->None: + + r""" + The main initializer of the class. + + Args: + ------------- + + - x: Input vector of shape (dim,). + - dim: `int`: Dimensionality of the input space. + - n_repetitions: `int`: The number of repetitions (possible permutations) denoting the permutation invariance of the function. + - num_peaks: `int`: Number of Gaussian peaks. + - instance: `Optional[int]`; an instance of np.random.RandomState for reproducibility. + - norm_type: `Optional[Union[str,int]]`: The norm to compute a penalty for not being close to the optimum value + """ + + # Input checks + if not (num_peaks >=1 and isinstance(num_peaks,int)): + raise ValueError("The number of peaks should be greater than 1") + + if not (dim >=1 and isinstance(dim,int)): + raise ValueError("The dimension of the problem should be a positive integer") + + if not (n_repetitions >=1 and isinstance(n_repetitions,int)): + raise ValueError("The number of repetitions should be a positive integer") + + # Check the divisibility of the dimension and repetitions + if not np.remainder(dim,n_repetitions)==0: + raise ValueError("The number of repetitions should be a divisor of the problem dimensionality!") + + #Instantiate a random state given the instance + random_state = np.random.RandomState(instance) + + # Store the norm type + self._norm_type:Union[int,str] = norm_type + + # Store the number of repetitions + self.__n_repetitions:int = n_repetitions + + # Generate random peak properties + self._peak_locations:np.ndarray = np.vstack( + ( + random_state.uniform(-3.99, 3.99, (1, dim)), + random_state.uniform(-4.99, 4.99, (num_peaks-1, dim)) + ) + ) + + self._peak_heights:List[float] = [] + + self._q_list:List[np.ndarray] = [] + self._r_list:List[np.matrix] = [] + + + # Compute the covariance matrices and compute the spectral eigendecomposition + for i in range(num_peaks): + + if i == 0: + self._peak_heights.append(10) + else: + self._peak_heights.append(1.1+8*(i)/(num_peaks-1)) + + # Generate random matrices + cov = random_state.uniform(-1,1,(dim, dim)) + + # Ensure the positive definiteness + cov = np.dot(cov.T,cov) + + # "Normalize the covariance" + cov = cov/n_repetitions**2 + + # Perform the computation + q, r = np.linalg.eigh(cov) + self._q_list.append(q) + self._r_list.append(r) + + name = "Generalized Gallagher Function with {0} peaks and {1} repetitions".format(num_peaks,n_repetitions) + bounds = RealBounds(dim,-5,5) + optimum = RealSolution( + x=self._peak_locations[0].tolist(), + y=self._compute_value(self._peak_locations[0]) + ) + + # Initialize the superclass + super().__init__( + name=name, + n_variables=dim, + instance=instance, + is_minimization=True, + bounds=bounds, + constraints=[], + optimum= optimum + ) + + def _compute_value(self,x:Union[np.ndarray,List[float]])->float: + r""" + This function computes the value of the function (without the penalties) + + Args: + ----------------- + - x: `Union[np.ndarray,List[float]]`: The vector denoting some point to compute the function. + """ + + # Compute the dimensionality (again!) + dim = self._peak_locations.shape[1] + + if isinstance(x,list): + x = np.asarray(x) + + # Reshape the array + x = x.ravel() + + # Compute the function value + values = [] + + for i in range(len(self._peak_locations)): + cur_peak_height = self._peak_heights[i] + cur_peak_location:np.ndarray = self._peak_locations[i] + cur_eigenvec:np.matrix = self._r_list[i] + cur_eigenvals:np.ndarray = self._q_list[i] + + if self.n_repetitions == 1: + diff = (x - cur_peak_location).reshape(-1,1) + exponent = (-(1/(2*dim)) * (diff.T @ ( cur_eigenvec @ np.diag(np.divide(1,cur_eigenvals) ) @ cur_eigenvec.T ) @ diff)).ravel()[0] + value = cur_peak_height * np.exp(exponent) + values.append(value) + else: + + # Get some general indices + gen_indices:np.ndarray = np.arange(start=0,stop=dim,dtype=int) + + # Regroup the indices into groups + group_indices:np.ndarray = gen_indices.reshape( + (self.n_repetitions,-1) + + ) + for k, shuff in enumerate(permutations(range(self.n_repetitions), + self.n_repetitions)): + + + shuff_indices = group_indices[np.asarray(shuff),:].ravel() + + + shuff_peak_location = cur_peak_location[shuff_indices] + shuff_eigenvals = cur_eigenvals[shuff_indices] + + shuff_eigenvec = cur_eigenvec[:,shuff_indices] + shuff_eigenvec = shuff_eigenvec[shuff_indices,:] + + diff = (x -shuff_peak_location).reshape(-1,1) + exponent = (-(1/(2*dim)) * (diff.T @ ( shuff_eigenvec @ np.diag(np.divide(1,shuff_eigenvals) ) @ shuff_eigenvec.T ) @ diff)).ravel()[0] + value = cur_peak_height* np.exp(exponent) + values.append(value) + + + + return (10-np.max(values))**2 + + def evaluate(self,x:Union[np.ndarray,List[float]])->float: + r""" + This is an overload of the evaluate function to compute the raw function + """ + + # Compute the dimensionality (again!) + dim = self._peak_locations.shape[1] + + if isinstance(x,list): + x = np.asarray(x) + + # Reshape the array + x = x.ravel() + + return self._compute_value(x) + _fopt(x=x,xopt=self._peak_locations[0],norm_type=self._norm_type,n_repetitions=self.n_repetitions) + _fpen(x) + + def create(self, id, iid, dim): + raise NotImplementedError + + @property + def n_repetitions(self)->int: + r""" + Returns the number of repetitions loaded + """ + return self.__n_repetitions + + @n_repetitions.setter + def n_repetitions(self,new_input:int)->None: + r""" + This is the setter of the number of repetitions + """ + if not (isinstance(new_input,int) and new_input >=1): + raise ValueError("The number of repetitions is not a positive integer") + + if not np.remainder(self._peak_locations.shape[1],new_input)==0: + raise ValueError("The number of repetitions should be a divisor of the problem dimensionality!") + + # Assign the new number of repetitions + self.__n_repetitions = new_input + + + def group_size(self)->int: + r""" + Returns the size of each group generated by the number of repetitions + """ + + return int(self._peak_locations.shape[1]/self.n_repetitions) + + def __del__(self, *args, **kwargs): + + self.detach_logger() + + + + + +# Example usage: +#x = #np.array([3.73,0.376]) +# x = np.array([-5,4.1,3,2,1]*10) +# funcc = GeneralizedGallagherFunction(num_peaks=101, +# dim=50, +# instance=5, +# norm_type=2) +# value = funcc(x) + +# print("Function value at x:", value) +# print(f"Function value at optimum: x={funcc.optimum.x}, ", funcc.optimum.y) diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/LogInfoExtension.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/LogInfoExtension.py new file mode 100644 index 0000000..7a8f754 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/LogInfoExtension.py @@ -0,0 +1,23 @@ +from ioh.iohcpp import LogInfo +from ioh.iohcpp.logger.property import AbstractProperty +from typing import List, ClassVar, Tuple, Dict + + + +### ---------------------------------------------------- +### CLASS DEFINITION +### ---------------------------------------------------- + + +class Regret(AbstractProperty): + + def name(self)->str: + return "Regret" + + def call_to_string(self, arg0, arg1): + return super().call_to_string(arg0, arg1) + + def __call__(self, arg0): + return super().__call__(arg0) + + diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/ModularMetaData.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/ModularMetaData.py new file mode 100644 index 0000000..3c3a94d --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/ModularMetaData.py @@ -0,0 +1,43 @@ +from ioh.iohcpp import MetaData +from ioh.iohcpp import OptimizationType + +''' +This code is defined as some extension to define modular problems from the +BBOB framework. +@Author: + - Ivan Olarte Rodriguez + +''' + +class ModularMetaData(MetaData): + r""" + This is an overload of the problem meta-data to fit with the requirements from modularity + """ + + def __init__(self, + problem_id:int, + instance:int, + name:str, + n_variables:int, + optimization_type:OptimizationType, + latent_dimensionality:int): + + # Initialise the parent class + super().__init__(problem_id, instance, name, n_variables, optimization_type) + + #Instantiate the latent dimensionality + self.__latent_dimensionality:int = latent_dimensionality + + @property + def latent_dimensionality(self)->int: + """ + Return the latent dimensionality of the modular problem + """ + return self.__latent_dimensionality + + @property + def n_repetitions(self)->int: + """ + Return the number of repetitions of the "latent dimensionality" + """ + return int(self.n_variables/self.__latent_dimensionality) \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/__pycache__/Generalized_Gallagher_Function.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/__pycache__/Generalized_Gallagher_Function.cpython-310.pyc new file mode 100644 index 0000000..0e45a43 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/__pycache__/Generalized_Gallagher_Function.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/__pycache__/Generalized_Gallagher_Function_with_repetitions.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/__pycache__/Generalized_Gallagher_Function_with_repetitions.cpython-310.pyc new file mode 100644 index 0000000..1fceb58 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/__pycache__/Generalized_Gallagher_Function_with_repetitions.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/some_main.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/some_main.py new file mode 100644 index 0000000..897b8b7 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/some_main.py @@ -0,0 +1,153 @@ +from Generalized_Gallagher_Function import GeneralizedGallagherFunction +from Generalized_Gallagher_Function_with_repetitions import GeneralizedGallagherFunctionWithRepetitions +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.ticker import LinearLocator +from matplotlib import cm +import ioh +import os +from cma import fmin, CMAEvolutionStrategy, CMAOptions + + +instance = 12 +run_e=80 + +ioh_prob = GeneralizedGallagherFunctionWithRepetitions(num_peaks=50, + dim=2, + n_repetitions=2, + instance=instance, + norm_type=1) + +print(ioh_prob.optimum.x,ioh_prob.optimum.y) +x_ = np.linspace(-5,5,num=100,endpoint=True) +y_ = np.linspace(-5,5,num=100,endpoint=True) + +xx, yy = np.meshgrid(x_,y_) + +zz = np.zeros_like(xx) + +for ii in range(np.shape(xx)[0]): + for jj in range(np.shape(xx)[1]): + zz[ii,jj] = ioh_prob([xx[ii,jj],yy[ii,jj]]) + + +fig1, ax2 = plt.subplots(layout='constrained') +CS = ax2.contourf(xx, yy, zz, 20, cmap=plt.cm.bone) +fig1.colorbar(CS, ax=ax2, shrink=0.9) + +plt.show() + +ioh_prob.reset() + +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + +# Plot the surface. +surf = ax.plot_surface(xx, yy, zz, cmap=plt.cm.bone, + linewidth=0, antialiased=False) + +# Customize the z axis. +#ax.set_zlim(-1.01, 1.01) +ax.zaxis.set_major_locator(LinearLocator(10)) +# A StrMethodFormatter is used automatically +ax.zaxis.set_major_formatter('{x:.02f}') + +ax.set_xlabel(f"$x_{0}$") +ax.set_ylabel(f"$x_{1}$") +ax.set_zlabel(f"$f(x_{0},x_{1})$") + + +# Add a color bar which maps values to colors. +fig.colorbar(surf, shrink=0.5, aspect=5) + +plt.show() + +ioh_prob.reset() + +triggers = [ + ioh.logger.trigger.Each(1), + ioh.logger.trigger.OnImprovement() +] + +logger = ioh.logger.Analyzer( + root=os.getcwd(), # Store data in the current working directory + folder_name=f"./ModularExperiments/Run_{run_e}", # in a folder named: 'my-experiment' + algorithm_name="CMA-ES", # meta-data for the algorithm used to generate these results + store_positions=True, # store x-variables in the logged files + triggers= triggers, + + additional_properties=[ + #ioh.logger.property.CURRENTY, # The constrained y-value, by default only the untransformed & unconstraint y + # value is logged. + ioh.logger.property.RAWYBEST, # Store the raw-best + ] + +) + +ioh_prob.attach_logger(logger) + +# class RandomSearch: +# 'Simple random search algorithm' +# def __init__(self, n: int, length: float = 0.0): +# self.n: int = n +# self.length: float = length + +# def __call__(self, problem: ioh.problem.RealSingleObjective) -> None: +# 'Evaluate the problem n times with a randomly generated solution' + +# for _ in range(self.n): +# # We can use the problems bounds accessor to get information about the problem bounds +# x = np.random.uniform(problem.bounds.lb, problem.bounds.ub) +# self.length = np.linalg.norm(x) + +# problem(x) + +x_init = 10*np.ravel(np.random.rand(1,ioh_prob.meta_data.n_variables))-5 +#u#u = SpecialInjectionCMA_ES(x_init, +# sigma0=1.25, +# budget=1000, random_seed=RANDOM_SEED, verbose = True, inopts=opts, +# ) +#uu(problem=ioh_prob, restarts=10,sorting_function= sorting_function) + +inopts:dict = {"maxfevals":20000, + "verb_filenameprefix":os.path.join(logger.output_directory,"outcmaes/"), + "bounds":[-5,5], + "seed":run_e, + "CMA_active":False} + +minimizer = CMAEvolutionStrategy(x0=x_init,sigma0=2.50,inopts=inopts) +fmin(ioh_prob,x0=minimizer,sigma0=None,restarts=10,options=inopts,bipop=True) +#obj:ioh.iohcpp.RealConstraint = ioh_prob.constraints[0] +#print(type(obj),obj.is_feasible(x_init),obj(x_init)) + +#cma.fmin2() +#es = cma.evolution_strategy.fmin2(objective_function=ioh_prob,x0=x_init.tolist(),sigma0=1.25,eval_initial_x=True,restarts=10,options=opts) +#es = cma.evolution_strategy.fmin_con2(objective_function=ioh_prob,x0=x_init.tolist(),sigma0=0.25,constraints=,eval_initial_x=True,restarts=10,options=opts) + + +# print(ioh_prob.constraints[0].is_feasible(x_init), +# ioh_prob.constraints[1].is_feasible(x_init), +# ioh_prob.constraints[2].is_feasible(x_init), +# ioh_prob.constraints[3].is_feasible(x_init)) + + + + +# If we want to perform multiple runs with the same objective function, after every run, the problem has to be reset, +# such that the internal state reflects the current run. +# def run_experiment(problem:ioh.problem.RealSingleObjective, algorithm:cma.CMAEvolutionStrategy, n_runs=5): +# for run in range(n_runs): + +# # Run the algorithm on the problem +# algorithm.optimize(objective_fct=problem,iterations=12,verb_disp=None) + +# # print the best found for this run +# print(f"run: {run+1} - best found:{problem.state.current_best.y: .3f}") + +# # Reset the problem +# problem.reset() + + +#run_experiment(problem=ioh_prob,algorithm=es,n_runs=1) + +logger.close() +ioh_prob.detach_logger() \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/some_main_general.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/some_main_general.py new file mode 100644 index 0000000..ea15aa1 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/Generalized_Gallagher_Functions/some_main_general.py @@ -0,0 +1,64 @@ +from Generalized_Gallagher_Function import GeneralizedGallagherFunction +from Generalized_Gallagher_Function_with_repetitions import GeneralizedGallagherFunctionWithRepetitions +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.ticker import LinearLocator +from matplotlib import cm +import ioh +import os +from cma import fmin, CMAEvolutionStrategy, CMAOptions + + +instance = 20 +run_e=92 + +ioh_prob = GeneralizedGallagherFunctionWithRepetitions(num_peaks=33, + dim=3, + n_repetitions=3, + instance=instance, + norm_type=1) + +print(ioh_prob.optimum.x,ioh_prob.optimum.y) + +triggers = [ + ioh.logger.trigger.Each(1), + ioh.logger.trigger.OnImprovement() +] + +logger = ioh.logger.Analyzer( + root=os.getcwd(), # Store data in the current working directory + folder_name=f"./ModularExperiments/Run_{run_e}", # in a folder named: 'my-experiment' + algorithm_name="CMA-ES", # meta-data for the algorithm used to generate these results + store_positions=True, # store x-variables in the logged files + triggers= triggers, + + additional_properties=[ + #ioh.logger.property.CURRENTY, # The constrained y-value, by default only the untransformed & unconstraint y + # value is logged. + ioh.logger.property.RAWYBEST, # Store the raw-best + ] + +) + +ioh_prob.attach_logger(logger) + + +x_init = 10*np.ravel(np.random.rand(1,ioh_prob.meta_data.n_variables))-5 +#u#u = SpecialInjectionCMA_ES(x_init, +# sigma0=1.25, +# budget=1000, random_seed=RANDOM_SEED, verbose = True, inopts=opts, +# ) +#uu(problem=ioh_prob, restarts=10,sorting_function= sorting_function) + +inopts:dict = {"maxfevals":20000, + "verb_filenameprefix":os.path.join(logger.output_directory,"outcmaes/"), + "bounds":[-5,5], + "seed":run_e, + "CMA_active":False} + +minimizer = CMAEvolutionStrategy(x0=x_init,sigma0=2.50,inopts=inopts) +fmin(ioh_prob,x0=minimizer,sigma0=None,restarts=10,options=inopts,bipop=False) + + +logger.close() +ioh_prob.detach_logger() \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularBBOBProblem.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularBBOBProblem.py new file mode 100644 index 0000000..da3fd1a --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularBBOBProblem.py @@ -0,0 +1,172 @@ + +# Import the libraries +import numpy as np +import ioh +from typing import List, Union, Tuple, Optional, Callable, Dict +from .ModularMetaData import ModularMetaData + +''' +This code is defined as some extension to define modular problems from the +BBOB framework. +@Author: + - Ivan Olarte Rodriguez + +''' + + + +class ModularBBOBProblem(ioh.iohcpp.problem.RealSingleObjective): + """ + This class is defined to perform a Single Objective Optimization based by building blocks of + instances of the BBOB functions. + + Each target corresponds to the arithmetic mean of multiple instances of the BBOB defined on + some latent dimensions. + """ + + #def __init__(self, name, n_variables, instance, is_minimization, bounds, constraints, optimum): + def __init__(self, + fid:Union[int,str], + instance:int, + latent_dimensionality:int, + n_repetitions:int)->object: + + r""" + This is the initialiser of the class. This takes the following parameters: + + Args: + ------------- + - fid: The problem identifier from the BBOB (an integer between 1 to 24 or a name) + - instance: an integer with the defined instance of the problem. + - latent_dimensionality: The intrinsic dimensionality of the module (positive integer) + - n_repetitions: The times the latent dimension is repeated. + """ + + # Get one of the problems by using the normal IOH calls to instantiate a problem + if isinstance(fid,str): + prob_Id:int = ioh.get_problem_id(fid,ioh.ProblemClass.BBOB) + else: + prob_Id:int = fid + + # Generate an instance of the intrinsic/latent problem + self.__intrinsic_problem:ioh.problem.RealSingleObjective = ioh.get_problem(fid=prob_Id, + instance=instance, + dimension=latent_dimensionality, + problem_class=ioh.ProblemClass.BBOB) + + #Extract the constraints and bounds as objects from intrinsic/latent problem + actual_bounds = self.__intrinsic_problem.bounds + actual_constraints:ioh.iohcpp.RealConstraintSet = [] + + # Extract the optimum information from actual instance + intrinsic_optimum:ioh.iohcpp.RealSolution = self.__intrinsic_problem.optimum + + actual_optimum = ioh.iohcpp.RealSolution( + x=np.tile(intrinsic_optimum.x,n_repetitions).ravel(), + y=intrinsic_optimum.y + + ) + + # Check the minimisation type + if self.__intrinsic_problem.meta_data.optimization_type == ioh.iohcpp.MIN: + isminimisation = True + else: + isminimisation = False + + #Initialize the superclass, being the BBOB class from IOH + super().__init__( "Modular_" + self.__intrinsic_problem.meta_data.name,#name + latent_dimensionality*n_repetitions,#n_variables + instance, #instance + isminimisation, #is_minimization + actual_bounds, #bounds + actual_constraints, #constraints + actual_optimum #optimum + ) + + # Overload the MetaData + self.__meta_data = ModularMetaData(problem_id=self.__intrinsic_problem.meta_data.problem_id, + instance= self.__intrinsic_problem.meta_data.instance, + name= "Modular_" + self.__intrinsic_problem.meta_data.name, + n_variables=latent_dimensionality*n_repetitions, + optimization_type=self.__intrinsic_problem.meta_data.optimization_type, + latent_dimensionality=latent_dimensionality + ) + + super().set_id(self.__meta_data.problem_id) + + def evaluate(self, + x:np.ndarray)->float: + r""" + This is an overload of the evaluate function from IOH. This will be computed as + the average of each evaluation of the intrinsic problem. + + Args: + ------- + - x (`np.ndarray`): An array (preferably NumPy) to compute the target + """ + + # Convert the array to Numpy (to be safe) + x_mod:np.ndarray = np.array(x) + + # Reshape the array to evaluate the intrinsic function + x_reshape:np.ndarray = x_mod.reshape((self.meta_data.n_repetitions,self.meta_data.latent_dimensionality)) + + current_sum:float = 0.0 + + for _,arr in enumerate(x_reshape): + current_sum += self.__intrinsic_problem(arr) + + return current_sum/self.meta_data.n_repetitions + + + def create(self, id, iid, dim): + """ + This is defined as some overload function. However, this will not be used and raises a `NotImplementedError` + """ + raise NotImplementedError() + + def reset(self)->None: + """ + Overload of reset function from the super class + """ + # Perform the reset method by following the super class + super().reset() + + # Reset the intrinsic problem + self.__intrinsic_problem.reset() + + def attach_logger_to_intrinsic_problem(self, + logger:ioh.logger.AbstractLogger)->None: + + """ + This function appends a separate data logger in case for the intrinsic problem + """ + + self.__intrinsic_problem.attach_logger(logger) + + def detach_logger_from_intrinsic_problem(self)->None: + """ + This function detaches the logger from the intrinsic problem + """ + return self.__intrinsic_problem.detach_logger() + + + def detach_logger(self)->None: + r""" + This is an overload function from the super class. To be safe, this detaches the logger from this instance and the intrinsic problem instance + """ + + # Use the super_class definition + super().detach_logger() + + self.detach_logger_from_intrinsic_problem() + + # @property + # def intrinsic_problem(self)->ioh.problem.BBOB: + # "This property just returns the intrinsic problem computed by using the BBOB" + # return self.__intrinsic_problem + + @property + def meta_data(self)->ModularMetaData: + "This property explotes the overload of the meta-data" + return self.__meta_data \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularLogInfo.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularLogInfo.py new file mode 100644 index 0000000..3af5a73 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularLogInfo.py @@ -0,0 +1,23 @@ +from ioh.iohcpp import LogInfo +from ioh.iohcpp import RealSolution + + +class ModularLogInfo(LogInfo): + def __init__(self, + evaluations: int, + raw_y_best: float, + transformed_y: float, + transformed_y_best: float, + current: RealSolution, + optimum: RealSolution): + # Add any additional initialization here + + # Call the parent class's __init__ method + super().__init__(evaluations, + raw_y_best, + transformed_y, + transformed_y_best, + current, + optimum) + + # Add any additional methods or overwrite existing methods as needed \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularMetaData.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularMetaData.py new file mode 100644 index 0000000..3c3a94d --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularMetaData.py @@ -0,0 +1,43 @@ +from ioh.iohcpp import MetaData +from ioh.iohcpp import OptimizationType + +''' +This code is defined as some extension to define modular problems from the +BBOB framework. +@Author: + - Ivan Olarte Rodriguez + +''' + +class ModularMetaData(MetaData): + r""" + This is an overload of the problem meta-data to fit with the requirements from modularity + """ + + def __init__(self, + problem_id:int, + instance:int, + name:str, + n_variables:int, + optimization_type:OptimizationType, + latent_dimensionality:int): + + # Initialise the parent class + super().__init__(problem_id, instance, name, n_variables, optimization_type) + + #Instantiate the latent dimensionality + self.__latent_dimensionality:int = latent_dimensionality + + @property + def latent_dimensionality(self)->int: + """ + Return the latent dimensionality of the modular problem + """ + return self.__latent_dimensionality + + @property + def n_repetitions(self)->int: + """ + Return the number of repetitions of the "latent dimensionality" + """ + return int(self.n_variables/self.__latent_dimensionality) \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblem.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblem.py new file mode 100644 index 0000000..25c89e5 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblem.py @@ -0,0 +1,603 @@ +# Import the libraries +import numpy as np +import ioh +from ioh import wrap_problem +from ioh import ProblemClass +from ioh.iohcpp import MAX,MIN +from typing import List, Union, Tuple, Optional, Callable, Dict +from .ModularMetaData import ModularMetaData +from .ModularLogInfo import ModularLogInfo +from ioh.iohcpp import RealSolution + +''' +This code is defined as some extension to define modular problems from the +BBOB framework. +@Author: + - Ivan Olarte Rodriguez + +''' + +def bimodal_function(x:np.ndarray)->float: + r""" + Evaluate the function x**5 - x**3 + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + if x.size == 1: + result = 200*((x[0]/5)**5 -(x[0]/5)**3) + else: + result = np.nan + + return result + +def multimodal_function(x:np.ndarray)->float: + r""" + Evaluate the function 1 + x**2 - cos(10x) + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + if x.size == 1: + result = 1 + x[0]**2 - np.cos(20*x[0]) + else: + result = np.nan + + return result + +def plateau_function(x:np.ndarray)->float: + r""" + Evaluate the function (1-x)*exp(-x**2) + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + if x.size == 1: + result = (1-x[0])*np.exp(-x[0]**2) + else: + result = np.nan + + return result + +def ackley(x:np.ndarray)->float: + r""" + Evaluate the Ackley function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + if x.size == 1: + result = -20*np.exp(-0.2*np.sqrt(0.5*(x[0]**2))) - np.exp(0.5*(np.cos(2*np.pi*x[0]))) + else: + result = np.nan + + return result + +def gramacy_lee(x:np.ndarray)->float: + r""" + Evaluate the Gramacy & Lee function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + # Transform the input to fit into -5 to 5 + xmin_new = 0.5 + xmax_new = 2.5 + + xmax_old = 5.0 + xmin_old = -5.0 + + # Rescale the x + x = (x-xmin_old)/(xmax_old-xmin_old)*(xmax_new-xmin_new) + xmin_new + + + + if x.size == 1: + result = np.sin(10*np.pi*x[0])/(2*x[0]) + (x[0]-1)**4 + else: + result = np.nan + + return result + +def levy(x:np.ndarray)->float: + r""" + Evaluate the Levy function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + if x.size == 1: + w = 1 + (x[0]*2-1)/4 + result = (np.sin(np.pi*w)**2 + + (w-1)**2*(1+10*np.sin(np.pi*w+1)**2) + + (w-1)**2*(1+np.sin(2*np.pi*w)**2)) + else: + result = np.nan + + return result + +def rastrigin(x:np.ndarray)->float: + r""" + Evaluate the Rastrigin function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + if x.size == 1: + result = 10*x[0]**2 - 10*np.cos(2*np.pi*x[0]) + else: + result = np.nan + + return result + +def schwefel(x:np.ndarray)->float: + r""" + Evaluate the Schwefel function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel()*100 + + result:float = 2000000 + + if x.size == 1: + result = 418.9829 - x[0]*np.sin(np.sqrt(np.abs(x[0]))) + else: + result = np.nan + + return result + +def sum_different_powers_function(x:np.ndarray)->float: + r""" + Evaluate the sum of different powers function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + if x.size == 1: + result = np.sum(np.abs(x)**(np.arange(1,x.size+1))) + else: + result = np.nan + + return result + +def forrester(x:np.ndarray)->float: + r""" + Evaluate the Forrester function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + # Modify the input to fit into the range 0 to 1 from -5 to 5 + x = (x+5)/10 + + result:float = 2000000 + + if x.size == 1: + result = (6*x[0]-2)**2*np.sin(12*x[0]-4) + else: + result = np.nan + + return result + +def sphere(x:np.ndarray)->float: + r""" + Evaluate the Sphere function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + if x.size == 1: + result = np.sum(x**2) + else: + result = np.nan + + return result + +def alpine(x:np.ndarray)->float: + r""" + Evaluate the Alpine function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + if x.size == 1: + norm_x = np.linalg.norm(x,ord=2) # Compute Euclidean norm (sqrt of sum of squares) + result = 1 - np.cos(2 * np.pi * norm_x) + 0.1 * norm_x + else: + result = np.nan + + return result + +def styblinski_tank(x:np.ndarray)->float: + r""" + Evaluate the Styblinski-Tank function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + if x.size == 1: + result = 0.5*(x[0]**4-16*x[0]**2+5*x[0]) + else: + result = np.nan + + return result + +def salomon(x:np.ndarray)->float: + r""" + Evaluate the Salomon function + + Args: + ----------- + - x: The input array whose size shall be equal to 1 + """ + + x = np.array(x).ravel() + + result:float = 2000000 + + if x.size == 1: + result = 1-np.cos(2*np.pi*np.sqrt(x[0]**2)) + 0.1*np.sqrt(x[0]**2) + else: + result = np.nan + + return result + + + +class ModularNonBBOBProblem(ioh.iohcpp.problem.RealSingleObjective): + """ + This class is defined to perform a Single Objective Optimization based by building blocks of + instances 1D problems. + + Each target corresponds to the arithmetic mean of multiple instances of the functions defined + on some 1D problems + """ + + __namespace_dict:dict = {"bimodal":bimodal_function, + "multimodal":multimodal_function, + "plateau":plateau_function, + "ackley":ackley, + "gramacy_lee":gramacy_lee, + "levy":levy, + "rastrigin":rastrigin, + "schwefel":schwefel, + "sum_different_powers":sum_different_powers_function, + "forrester":forrester, + "sphere":sphere, + "alpine":alpine, + "styblinski_tank":styblinski_tank, + "salomon":salomon} + + __idx_space_dict:dict = {1:"bimodal", + 2:"multimodal", + 3:"plateau", + 4:"ackley", + 5:"gramacy_lee", + 6:"levy", + 7:"rastrigin", + 8:"schwefel", + 9:"sum_different_powers", + 10:"forrester", + 11:"sphere", + 12:"alpine", + 13:"styblinski_tank", + 14:"salomon"} + + __optimum_dict:dict = {"bimodal":{'x':5*np.sqrt(3/5), + 'y':bimodal_function(5*np.sqrt(3/5))}, + "multimodal":{'x':0.0, + 'y':multimodal_function(0)}, + "plateau":{'x':0.5+np.sqrt(3/4), + 'y':plateau_function(0.5+np.sqrt(3/4))}, + "ackley":{'x':0.0, 'y':ackley(0.0)}, + "gramacy_lee":{'x':-4.757183, 'y':gramacy_lee(-4.757183)}, + "levy":{'x':0.5, 'y':levy(0.5)}, + "rastrigin":{'x':0.0, 'y':rastrigin(0.0)}, + "schwefel":{'x':420.9687/100, 'y':schwefel(420.9687/100)}, + "sum_different_powers":{'x':0, 'y':sum_different_powers_function(0.0)}, + "forrester":{'x':0.7572*10-5, 'y':forrester(0.7572*10-5)}, + "sphere":{'x':0.0, 'y':sphere(0.0)}, + "alpine":{'x':0.0, 'y':alpine(0.0)}, + "styblinski_tank":{'x':-2.903534, 'y':styblinski_tank(-2.903534)}, + "salomon":{'x':0.0, 'y':salomon(0.0)}} + + __modes:dict = {"average":1,"maximum":2} + + __latent_dimensionality:int = 1 + __instance:int = 0 + + #def __init__(self, name, n_variables, instance, is_minimization, bounds, constraints, optimum): + def __init__(self, + fid:Union[int,str], + n_repetitions:int, + mode:Optional[Union[str,int]]="average")->object: + + r""" + This is the initialiser of the class. This takes the following parameters: + + Args: + ------------- + - fid: The problem identifier "either a name or an identifier between 1 to 10" + - n_repetitions: The times the latent dimension is repeated. + """ + + # Start the function and name as some empty variables + func:Callable = None + func_name:str = None + func_id:int = 0 + + # Get one of the problems + if isinstance(fid,str): + # Check if the string matches one of the names + if fid in [*self.__namespace_dict]: + func = self.__namespace_dict[fid] + func_name = fid + + # Do the inverse mapping of the dictionary + inv_map:dict = {v: k for k, v in self.__idx_space_dict.items()} + # Get the id + func_id = inv_map[fid] + else: + raise AttributeError(f"The fid given is a string that does not match the options in {[*self.__namespace_dict]}", + fid,"fid") + elif isinstance(fid,int): + + # Check if the integer is included in the set + if fid in [*self.__idx_space_dict]: + # Set the id + func_id = fid + # Get the value + func_name = self.__idx_space_dict[fid] + func = self.__namespace_dict[func_name] + + else: + raise AttributeError(f"The fid given is not an integer included in the set: {[*self.__idx_space_dict]}", + "fid",fid) + else: + raise AttributeError(f"The fid given is not an integer or a string", + "fid",fid) + + # Set the mode + self.mode = mode + + + # Extract the optimum information from actual instance + intrinsic_optimum = RealSolution(x=[self.__optimum_dict[func_name]['x']], + y=self.__optimum_dict[func_name]['y']) + + # Define a wrapper + self.__intrinsic_problem:ioh.problem.RealSingleObjective = wrap_problem(function=func, + name=func_name, + dimension=self.__latent_dimensionality, + instance=self.__instance, + optimization_type=MIN, + lb=-5, + ub=5 + ) + + #Extract the constraints and bounds as objects from intrinsic/latent problem + actual_bounds = self.__intrinsic_problem.bounds + actual_constraints:ioh.iohcpp.RealConstraintSet = [] + + + + + + actual_optimum = RealSolution( + x=np.tile(intrinsic_optimum.x,n_repetitions).ravel(), + y=intrinsic_optimum.y) + + # Check the minimisation type + if self.__intrinsic_problem.meta_data.optimization_type == ioh.iohcpp.MIN: + isminimisation = True + else: + isminimisation = False + + #Initialize the superclass, being the BBOB class from IOH + super().__init__( "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name,#name + self.__latent_dimensionality*n_repetitions,#n_variables + self.__instance, #instance + isminimisation, #is_minimization + actual_bounds, #bounds + actual_constraints, #constraints + actual_optimum #optimum + ) + + # Set the function id + super().set_id(func_id) + + # Overload the MetaData + self.__meta_data = ModularMetaData(problem_id=self.__intrinsic_problem.meta_data.problem_id, + instance= self.__intrinsic_problem.meta_data.instance, + name= "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name, + n_variables=self.__latent_dimensionality*n_repetitions, + optimization_type=self.__intrinsic_problem.meta_data.optimization_type, + latent_dimensionality=self.__latent_dimensionality + ) + + def evaluate(self, + x:np.ndarray)->float: + r""" + This is an overload of the evaluate function from IOH. This will be computed as + the average of each evaluation of the intrinsic problem. + + Args: + ------- + - x (`np.ndarray`): An array (preferably NumPy) to compute the target + """ + + # Convert the array to Numpy (to be safe) + x_mod:np.ndarray = np.array(x) + + # Reshape the array to evaluate the intrinsic function + x_reshape:np.ndarray = x_mod.reshape((self.meta_data.n_repetitions,self.meta_data.latent_dimensionality)) + + # This is to set in case the mode is set as the average + if self.mode == "average": + current_sum:float = 0.0 + + for _,arr in enumerate(x_reshape): + current_sum += self.__intrinsic_problem(arr) + + return current_sum/self.meta_data.n_repetitions + + # This is to set in case the mode is set as the maximum + elif self.mode == "maximum": + current_max:float = -np.inf + + for _,arr in enumerate(x_reshape): + current_max = max(current_max,self.__intrinsic_problem(arr)) + + return current_max + + + def create(self, id, iid, dim): + """ + This is defined as some overload function. However, this will not be used and raises a `NotImplementedError` + """ + raise NotImplementedError() + + def reset(self)->None: + """ + Overload of reset function from the super class + """ + # Perform the reset method by following the super class + super().reset() + + # Reset the intrinsic problem + self.__intrinsic_problem.reset() + + def attach_logger_to_intrinsic_problem(self, + logger:ioh.logger.AbstractLogger)->None: + + """ + This function appends a separate data logger in case for the intrinsic problem + """ + + self.__intrinsic_problem.attach_logger(logger) + + def detach_logger_from_intrinsic_problem(self)->None: + """ + This function detaches the logger from the intrinsic problem + """ + return self.__intrinsic_problem.detach_logger() + + + def detach_logger(self)->None: + r""" + This is an overload function from the super class. To be safe, this detaches the logger from this instance and the intrinsic problem instance + """ + + # Use the super_class definition + super().detach_logger() + + self.detach_logger_from_intrinsic_problem() + + # @property + # def intrinsic_problem(self)->ioh.problem.BBOB: + # "This property just returns the intrinsic problem computed by using the BBOB" + # return self.__intrinsic_problem + + @property + def meta_data(self)->ModularMetaData: + "This property explotes the overload of the meta-data" + return self.__meta_data + + @property + def mode(self)->str: + "This property returns the mode of the problem" + return self.__mode + + @mode.setter + def mode(self, mode:Union[str,int])->None: + "This property sets the mode of the problem" + + if isinstance(mode,str): + if mode in [*self.__modes]: + self.__mode = mode + else: + raise AttributeError(f"The mode given is a string that does not match the options in {[*self.__modes]}", + mode,"mode") + + elif isinstance(mode,int): + if mode in [*self.__modes.values()]: + self.__mode = mode + else: + raise AttributeError(f"The mode given is an integer that does not match the options in {[*self.__modes.values()]}", + mode,"mode") + + else: + raise AttributeError(f"The mode given is not an integer or a string", + mode,"mode") + + diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithDistance.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithDistance.py new file mode 100644 index 0000000..4d4d8b4 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithDistance.py @@ -0,0 +1,411 @@ +# Import the libraries +import numpy as np +import ioh +from ioh import wrap_problem +from ioh import ProblemClass +from ioh.iohcpp import MAX,MIN +from typing import List, Union, Tuple, Optional, Callable, Dict +from .ModularMetaData import ModularMetaData +from ioh.iohcpp import RealSolution +from .ModularNonBBOBProblem import (bimodal_function, + multimodal_function, + plateau_function, + ackley, + gramacy_lee, + levy, + rastrigin, + schwefel, + sum_different_powers_function, + forrester, + sphere, + alpine, + styblinski_tank, + salomon) + +''' +This code is defined as some extension to define modular problems from the +BBOB framework. +@Author: + - Ivan Olarte Rodriguez + +''' + + +class ModularNonBBOBProblemWithDistance(ioh.iohcpp.problem.RealSingleObjective): + """ + This class is defined to perform a Single Objective Optimization based by building blocks of + instances 1D problems. + + In this case, the problem is defined as a linear combination of each of the parameters. + """ + + __namespace_dict:dict = {"bimodal":bimodal_function, + "multimodal":multimodal_function, + "plateau":plateau_function, + "ackley":ackley, + "gramacy_lee":gramacy_lee, + "levy":levy, + "rastrigin":rastrigin, + "schwefel":schwefel, + "sum_different_powers":sum_different_powers_function, + "forrester":forrester, + "sphere":sphere, + "alpine":alpine, + "styblinski_tank":styblinski_tank, + "salomon":salomon} + + __idx_space_dict:dict = {1:"bimodal", + 2:"multimodal", + 3:"plateau", + 4:"ackley", + 5:"gramacy_lee", + 6:"levy", + 7:"rastrigin", + 8:"schwefel", + 9:"sum_different_powers", + 10:"forrester", + 11:"sphere", + 12:"alpine", + 13:"styblinski_tank", + 14:"salomon"} + + __optimum_dict:dict = {"bimodal":{'x':5*np.sqrt(3/5), + 'y':bimodal_function(5*np.sqrt(3/5))}, + "multimodal":{'x':0.0, + 'y':multimodal_function(0)}, + "plateau":{'x':0.5+np.sqrt(3/4), + 'y':plateau_function(0.5+np.sqrt(3/4))}, + "ackley":{'x':0.0, 'y':ackley(0.0)}, + "gramacy_lee":{'x':-4.757183, 'y':gramacy_lee(-4.757183)}, + "levy":{'x':0.5, 'y':levy(0.5)}, + "rastrigin":{'x':0.0, 'y':rastrigin(0.0)}, + "schwefel":{'x':420.9687/100, 'y':schwefel(420.9687/100)}, + "sum_different_powers":{'x':0, 'y':sum_different_powers_function(0.0)}, + "forrester":{'x':0.7572*10-5, 'y':forrester(0.7572*10-5)}, + "sphere":{'x':0.0, 'y':sphere(0.0)}, + "alpine":{'x':0.0, 'y':alpine(0.0)}, + "styblinski_tank":{'x':-2.903534, 'y':styblinski_tank(-2.903534)}, + "salomon":{'x':0.0, 'y':salomon(0.0)}} + + __modes:dict = {"average":1,"maximum":2} + + __latent_dimensionality:int = 1 + + #def __init__(self, name, n_variables, instance, is_minimization, bounds, constraints, optimum): + def __init__(self, + fid:Union[int,str], + n_repetitions:int, + norm_type:Union[str,int]=2, + instance:int=1, + )->object: + + r""" + This is the initialiser of the class. This takes the following parameters: + + Args: + ------------- + - fid: The problem identifier "either a name or an identifier between 1 to 10" + - n_repetitions: The times the latent dimension is repeated. + """ + + # Start the function and name as some empty variables + func:Callable = None + func_name:str = None + func_id:int = 0 + + # Get one of the problems + if isinstance(fid,str): + # Check if the string matches one of the names + if fid in [*self.__namespace_dict]: + func = self.__namespace_dict[fid] + func_name = fid + + # Do the inverse mapping of the dictionary + inv_map:dict = {v: k for k, v in self.__idx_space_dict.items()} + # Get the id + func_id = inv_map[fid] + else: + raise AttributeError(f"The fid given is a string that does not match the options in {[*self.__namespace_dict]}", + fid,"fid") + elif isinstance(fid,int): + + # Check if the integer is included in the set + if fid in [*self.__idx_space_dict]: + # Set the id + func_id = fid + # Get the value + func_name = self.__idx_space_dict[fid] + func = self.__namespace_dict[func_name] + + else: + raise AttributeError(f"The fid given is not an integer included in the set: {[*self.__idx_space_dict]}", + "fid",fid) + else: + raise AttributeError(f"The fid given is not an integer or a string", + "fid",fid) + + # Define the norm type and the instance + self.norm_type = norm_type + self.__instance = instance if instance > 0 else None # By doing this, an error will be generated if the instance is not valid + + + # Extract the optimum information from actual instance + intrinsic_optimum = RealSolution(x=[self.__optimum_dict[func_name]['x']], + y=self.__optimum_dict[func_name]['y']) + + # Instantiate this optimum as part of the class + self.__intrinsic_optimum = intrinsic_optimum + + + # Define a wrapper + self.__intrinsic_problem:ioh.problem.RealSingleObjective = wrap_problem(function=func, + name=func_name, + dimension=self.__latent_dimensionality, + instance=self.__instance, + optimization_type=MIN, + lb=-5, + ub=5 + ) + + # Set the id of the intrinsic problem as the same as the function id in this domain + self.__intrinsic_problem.set_id(func_id) + + # Get the origin (or one instance of the minimum) + self.__origin_point:np.ndarray = self._set_origins(n_repetitions) + + # Compute the maximum distance to be obtained given the bounds + self.__max_distance:float = self._get_max_distance(self.__origin_point,self.norm_type) + + + #Extract the constraints and bounds as objects from intrinsic/latent problem + actual_bounds = self.__intrinsic_problem.bounds + actual_constraints:ioh.iohcpp.RealConstraintSet = [] + + + actual_optimum = RealSolution( + x=self.__origin_point.ravel(), + y=intrinsic_optimum.y + ) + + # Check the minimisation type + if self.__intrinsic_problem.meta_data.optimization_type == ioh.iohcpp.MIN: + isminimisation = True + else: + isminimisation = False + + #Initialize the superclass, being the BBOB class from IOH + super().__init__( "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name,#name + self.__latent_dimensionality*n_repetitions,#n_variables + self.__instance, #instance + isminimisation, #is_minimization + actual_bounds, #bounds + actual_constraints, #constraints + actual_optimum #optimum + ) + + # Set the function id + super().set_id(func_id) + + # Overload the MetaData + self.__meta_data = ModularMetaData(problem_id=self.__intrinsic_problem.meta_data.problem_id, + instance= self.__intrinsic_problem.meta_data.instance, + name= "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name, + n_variables=self.__latent_dimensionality*n_repetitions, + optimization_type=self.__intrinsic_problem.meta_data.optimization_type, + latent_dimensionality=self.__latent_dimensionality + ) + + def _set_origins(self, n_repetitions)->np.ndarray: + r""" + This function is defined to set the origins "or the minimum" of the n-dimensional problem + """ + + # Set the instance as some random seed + ### TODO: Check if this is the correct way to set the seed + np.random.seed(self.instance) + + # Generate a random array + x = np.random.uniform(low=-5,high=5,size=(n_repetitions,)) + + # Sort the array + x = np.sort(x) + + # Return the array + return x + + @staticmethod + def _get_max_distance(reference_point:np.ndarray, norm_type:Union[str,int])->float: + r""" + This function is defined to compute the maximum possible distance within the bounds of the problem. + Note the bounds correspond to a rectangle in the n-dimensional space of size (-5,5)*dimensionality + + Args: + -------- + - reference_point (`np.ndarray`): The reference point to compute the distance + - norm_type (`Optional[Union[str,int]]`): The norm type to compute the distance. By default, this is the Euclidean norm. + """ + + import itertools + + # NOTE: This is a brute force implementation as this will compute a stupid cartesian product + lb:float = -5 + ub:float = 5 + + n_repetitions:int = reference_point.size + + product_:list = [x for x in itertools.product([lb,ub],repeat=n_repetitions)] + + # Now discard the nodes of the cartesian product which are not sorted + product_ = [x for x in product_ if all(y <= z for y, z in itertools.pairwise(x))] + + # Now convert the list to a numpy array + product_:np.ndarray = np.array(product_,dtype=np.float64,subok=False) + + max_dist:float = -np.inf + + # loop through the product + for _,x_ in enumerate(product_): + actual_dist:float = np.linalg.norm(x_.ravel()-reference_point.ravel(),ord=norm_type) + + if actual_dist > max_dist: + max_dist = actual_dist + + + # Return the maximum distance + return max_dist + + def _transform_range(self, dist_val:float)->float: + r""" + This function is defined to transform the range of the input array to the range of the intrinsic problem + """ + + # Get the optimum of the intrinsic problem + intrinsic_optimum:RealSolution = self.__intrinsic_optimum + x_opt = intrinsic_optimum.x + + init_range:tuple = (0,self.__max_distance) + final_range:tuple = (x_opt,5.0) + + func = (dist_val-init_range[0])/(init_range[1]-init_range[0])*(final_range[1]-final_range[0])+final_range[0] + + return func + + + def evaluate(self, + x:Union[np.ndarray,List[float]])->float: + r""" + This is an overload of the evaluate function from IOH. This will be computed as + the average of each evaluation of the intrinsic problem. + + Args: + ------- + - x (`np.ndarray`): An array (preferably NumPy) to compute the target + """ + + # Convert the array to Numpy (to be safe) + x_mod:np.ndarray = np.array(x, subok=False, copy=True) + + # Flatten the array and sort + x_sorted:np.ndarray = np.sort(x_mod.ravel()) + + # Compute the distance to the origin point + dist_from_origin:float = np.linalg.norm(x_sorted-self.__origin_point.ravel(),ord=self.norm_type) + + # apply the transformation + x_transformed:np.ndarray = self._transform_range(dist_from_origin) + + # Compute the intrinsic problem + intrinsic_eval:float = self.__intrinsic_problem(x_transformed) + + return intrinsic_eval + + # + #raise NotImplementedError("This function is not implemented yet") + + + def create(self, id, iid, dim): + """ + This is defined as some overload function. However, this will not be used and raises a `NotImplementedError` + """ + raise NotImplementedError() + + def reset(self)->None: + """ + Overload of reset function from the super class + """ + # Perform the reset method by following the super class + super().reset() + + # Reset the intrinsic problem + self.__intrinsic_problem.reset() + + def attach_logger_to_intrinsic_problem(self, + logger:ioh.logger.AbstractLogger)->None: + + """ + This function appends a separate data logger in case for the intrinsic problem + """ + + self.__intrinsic_problem.attach_logger(logger) + + def detach_logger_from_intrinsic_problem(self)->None: + """ + This function detaches the logger from the intrinsic problem + """ + return self.__intrinsic_problem.detach_logger() + + + def detach_logger(self)->None: + r""" + This is an overload function from the super class. To be safe, this detaches the logger from this instance and the intrinsic problem instance + """ + + # Use the super_class definition + super().detach_logger() + + self.detach_logger_from_intrinsic_problem() + + # @property + # def intrinsic_problem(self)->ioh.problem.BBOB: + # "This property just returns the intrinsic problem computed by using the BBOB" + # return self.__intrinsic_problem + + @property + def meta_data(self)->ModularMetaData: + "This property explotes the overload of the meta-data" + return self.__meta_data + + @property + def norm_type(self)->Union[str,int]: + "This property returns the norm type" + return self.__norm_type + + @norm_type.setter + def norm_type(self, value:Union[str,int])->None: + + "This property sets the norm type" + if isinstance(value,str): + # remove the spaces and convert to lower case + value = value.lower().strip() + if value in ["fro","nuc"]: + self.__norm_type = value + else: + raise AttributeError(f"The norm type given is not included in the set of modes: {['fro','nuc']}", + value,"norm_type") + elif isinstance(value,int): + if value in [1,2]: + self.__norm_type = value + else: + raise AttributeError(f"The norm type given is not included in the set of modes: {[1,2]}", + value,"norm_type") + else: + raise AttributeError(f"The norm type given is not a valid type", + value,"norm_type") + + @property + def instance(self)->int: + "This property returns the instance" + return self.__instance + + + \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithPerturbedDistance.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithPerturbedDistance.py new file mode 100644 index 0000000..35047cd --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithPerturbedDistance.py @@ -0,0 +1,396 @@ +# Import the libraries +import numpy as np +import ioh +from ioh import wrap_problem +from ioh import ProblemClass +from ioh.iohcpp import MAX,MIN +from typing import List, Union, Tuple, Optional, Callable, Dict +from .ModularMetaData import ModularMetaData +from ioh.iohcpp import RealSolution +from .ModularNonBBOBProblem import (bimodal_function, + multimodal_function, + plateau_function, + ackley, + gramacy_lee, + levy, + rastrigin, + schwefel, + sum_different_powers_function, + forrester, + sphere) + +''' +This code is defined as some extension to define modular problems from the +BBOB framework. +@Author: + - Ivan Olarte Rodriguez + +''' + + +class ModularNonBBOBProblemWithPerturbedDistance(ioh.iohcpp.problem.RealSingleObjective): + """ + This class is defined to perform a Single Objective Optimization based by building blocks of + instances 1D problems. + + In this case, the problem is defined as a linear combination of each of the parameters. + """ + + __namespace_dict:dict = {"bimodal":bimodal_function, + "multimodal":multimodal_function, + "plateau":plateau_function, + "ackley":ackley, + "gramacy_lee":gramacy_lee, + "levy":levy, + "rastrigin":rastrigin, + "schwefel":schwefel, + "sum_different_powers":sum_different_powers_function, + "forrester":forrester, + "sphere":sphere} + + __idx_space_dict:dict = {1:"bimodal", + 2:"multimodal", + 3:"plateau", + 4:"ackley", + 5:"gramacy_lee", + 6:"levy", + 7:"rastrigin", + 8:"schwefel", + 9:"sum_different_powers", + 10:"forrester", + 11:"sphere"} + + __optimum_dict:dict = {"bimodal":{'x':5*np.sqrt(3/5), + 'y':bimodal_function(5*np.sqrt(3/5))}, + "multimodal":{'x':0.0, + 'y':multimodal_function(0)}, + "plateau":{'x':0.5+np.sqrt(3/4), + 'y':plateau_function(0.5+np.sqrt(3/4))}, + "ackley":{'x':0.0, 'y':ackley(0.0)}, + "gramacy_lee":{'x':-4.757183, 'y':gramacy_lee(-4.757183)}, + "levy":{'x':1.0, 'y':levy(1.0)}, + "rastrigin":{'x':0.0, 'y':rastrigin(0.0)}, + "schwefel":{'x':420.9687/100, 'y':schwefel(420.9687/100)}, + "sum_different_powers":{'x':0, 'y':sum_different_powers_function(0.0)}, + "forrester":{'x':0.7572*10-5, 'y':forrester(0.7572*10-5)}, + "sphere":{'x':0.0, 'y':sphere(0.0)}} + + __modes:dict = {"average":1,"maximum":2} + + __latent_dimensionality:int = 1 + + #def __init__(self, name, n_variables, instance, is_minimization, bounds, constraints, optimum): + def __init__(self, + fid:Union[int,str], + n_repetitions:int, + norm_type:Union[str,int]=2, + instance:int=1, + )->object: + + r""" + This is the initialiser of the class. This takes the following parameters: + + Args: + ------------- + - fid: The problem identifier "either a name or an identifier between 1 to 10" + - n_repetitions: The times the latent dimension is repeated. + """ + + # Start the function and name as some empty variables + func:Callable = None + func_name:str = None + func_id:int = 0 + + # Get one of the problems + if isinstance(fid,str): + # Check if the string matches one of the names + if fid in [*self.__namespace_dict]: + func = self.__namespace_dict[fid] + func_name = fid + + # Do the inverse mapping of the dictionary + inv_map:dict = {v: k for k, v in self.__idx_space_dict.items()} + # Get the id + func_id = inv_map[fid] + else: + raise AttributeError(f"The fid given is a string that does not match the options in {[*self.__namespace_dict]}", + fid,"fid") + elif isinstance(fid,int): + + # Check if the integer is included in the set + if fid in [*self.__idx_space_dict]: + # Set the id + func_id = fid + # Get the value + func_name = self.__idx_space_dict[fid] + func = self.__namespace_dict[func_name] + + else: + raise AttributeError(f"The fid given is not an integer included in the set: {[*self.__idx_space_dict]}", + "fid",fid) + else: + raise AttributeError(f"The fid given is not an integer or a string", + "fid",fid) + + # Define the norm type and the instance + self.norm_type = norm_type + self.__instance = instance if instance > 0 else None # By doing this, an error will be generated if the instance is not valid + + + # Extract the optimum information from actual instance + intrinsic_optimum = RealSolution(x=[self.__optimum_dict[func_name]['x']], + y=self.__optimum_dict[func_name]['y']) + + # Instantiate this optimum as part of the class + self.__intrinsic_optimum = intrinsic_optimum + + + # Define a wrapper + self.__intrinsic_problem:ioh.problem.RealSingleObjective = wrap_problem(function=func, + name=func_name, + dimension=self.__latent_dimensionality, + instance=self.__instance, + optimization_type=MIN, + lb=-5, + ub=5 + ) + + # Set the id of the intrinsic problem as the same as the function id in this domain + self.__intrinsic_problem.set_id(func_id) + + # Get the origin (or one instance of the minimum) + self.__origin_point:np.ndarray = self._set_origins(n_repetitions) + + # Compute the maximum distance to be obtained given the bounds + self.__max_distance:float = self._get_max_distance(self.__origin_point,self.norm_type) + + + #Extract the constraints and bounds as objects from intrinsic/latent problem + actual_bounds = self.__intrinsic_problem.bounds + actual_constraints:ioh.iohcpp.RealConstraintSet = [] + + + actual_optimum = RealSolution( + x=self.__origin_point.ravel(), + y=intrinsic_optimum.y + ) + + # Check the minimisation type + if self.__intrinsic_problem.meta_data.optimization_type == ioh.iohcpp.MIN: + isminimisation = True + else: + isminimisation = False + + #Initialize the superclass, being the BBOB class from IOH + super().__init__( "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name,#name + self.__latent_dimensionality*n_repetitions,#n_variables + self.__instance, #instance + isminimisation, #is_minimization + actual_bounds, #bounds + actual_constraints, #constraints + actual_optimum #optimum + ) + + # Set the function id + super().set_id(func_id) + + # Overload the MetaData + self.__meta_data = ModularMetaData(problem_id=self.__intrinsic_problem.meta_data.problem_id, + instance= self.__intrinsic_problem.meta_data.instance, + name= "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name, + n_variables=self.__latent_dimensionality*n_repetitions, + optimization_type=self.__intrinsic_problem.meta_data.optimization_type, + latent_dimensionality=self.__latent_dimensionality + ) + + def _set_origins(self, n_repetitions)->np.ndarray: + r""" + This function is defined to set the origins "or the minimum" of the n-dimensional problem + """ + + # Set the instance as some random seed + ### TODO: Check if this is the correct way to set the seed + np.random.seed(self.instance) + + # Generate a random array + x = np.random.uniform(low=-5,high=5,size=(n_repetitions,)) + + # Sort the array + x = np.sort(x) + + # Return the array + return x + + @staticmethod + def _get_max_distance(reference_point:np.ndarray, norm_type:Union[str,int])->float: + r""" + This function is defined to compute the maximum possible distance within the bounds of the problem. + Note the bounds correspond to a rectangle in the n-dimensional space of size (-5,5)*dimensionality + + Args: + -------- + - reference_point (`np.ndarray`): The reference point to compute the distance + - norm_type (`Optional[Union[str,int]]`): The norm type to compute the distance. By default, this is the Euclidean norm. + """ + + import itertools + + # NOTE: This is a brute force implementation as this will compute a stupid cartesian product + lb:float = -5 + ub:float = 5 + + n_repetitions:int = reference_point.size + + product_:list = [x for x in itertools.product([lb,ub],repeat=n_repetitions)] + + # Now discard the nodes of the cartesian product which are not sorted + product_ = [x for x in product_ if all(y <= z for y, z in itertools.pairwise(x))] + + # Now convert the list to a numpy array + product_:np.ndarray = np.array(product_,dtype=np.float64,subok=False) + + max_dist:float = -np.inf + + # loop through the product + for _,x_ in enumerate(product_): + actual_dist:float = np.linalg.norm(x_.ravel()-reference_point.ravel(),ord=norm_type) + + if actual_dist > max_dist: + max_dist = actual_dist + + + # Return the maximum distance + return max_dist + + def _transform_range(self, dist_val:float)->float: + r""" + This function is defined to transform the range of the input array to the range of the intrinsic problem + """ + + # Get the optimum of the intrinsic problem + intrinsic_optimum:RealSolution = self.__intrinsic_optimum + x_opt = intrinsic_optimum.x + + init_range:tuple = (0,self.__max_distance) + final_range:tuple = (x_opt,5.0) + + func = (dist_val-init_range[0])/(init_range[1]-init_range[0])*(final_range[1]-final_range[0])+final_range[0] + + return func + + + def evaluate(self, + x:Union[np.ndarray,List[float]])->float: + r""" + This is an overload of the evaluate function from IOH. This will be computed as + the average of each evaluation of the intrinsic problem. + + Args: + ------- + - x (`np.ndarray`): An array (preferably NumPy) to compute the target + """ + + # Convert the array to Numpy (to be safe) + x_mod:np.ndarray = np.array(x, subok=False, copy=True) + + # Flatten the array and sort + x_sorted:np.ndarray = np.sort(x_mod.ravel()) + + # Compute the distance to the origin point + dist_from_origin:float = np.linalg.norm(x_sorted-self.__origin_point.ravel(),ord=self.norm_type) + + # apply the transformation + x_transformed:np.ndarray = self._transform_range(dist_from_origin) + + # Compute the intrinsic problem + intrinsic_eval:float = self.__intrinsic_problem(x_transformed) + + return intrinsic_eval + + # + #raise NotImplementedError("This function is not implemented yet") + + + def create(self, id, iid, dim): + """ + This is defined as some overload function. However, this will not be used and raises a `NotImplementedError` + """ + raise NotImplementedError() + + def reset(self)->None: + """ + Overload of reset function from the super class + """ + # Perform the reset method by following the super class + super().reset() + + # Reset the intrinsic problem + self.__intrinsic_problem.reset() + + def attach_logger_to_intrinsic_problem(self, + logger:ioh.logger.AbstractLogger)->None: + + """ + This function appends a separate data logger in case for the intrinsic problem + """ + + self.__intrinsic_problem.attach_logger(logger) + + def detach_logger_from_intrinsic_problem(self)->None: + """ + This function detaches the logger from the intrinsic problem + """ + return self.__intrinsic_problem.detach_logger() + + + def detach_logger(self)->None: + r""" + This is an overload function from the super class. To be safe, this detaches the logger from this instance and the intrinsic problem instance + """ + + # Use the super_class definition + super().detach_logger() + + self.detach_logger_from_intrinsic_problem() + + # @property + # def intrinsic_problem(self)->ioh.problem.BBOB: + # "This property just returns the intrinsic problem computed by using the BBOB" + # return self.__intrinsic_problem + + @property + def meta_data(self)->ModularMetaData: + "This property explotes the overload of the meta-data" + return self.__meta_data + + @property + def norm_type(self)->Union[str,int]: + "This property returns the norm type" + return self.__norm_type + + @norm_type.setter + def norm_type(self, value:Union[str,int])->None: + "This property sets the norm type" + if isinstance(value,str): + if value in ["average","maximum"]: + self.__norm_type = self.__modes[value] + else: + raise AttributeError(f"The norm type given is not included in the set of modes: {['fro','nuc']}", + value,"norm_type") + elif isinstance(value,int): + if value in [1,2]: + self.__norm_type = value + else: + raise AttributeError(f"The norm type given is not included in the set of modes: {[1,2]}", + value,"norm_type") + else: + raise AttributeError(f"The norm type given is not a valid type", + value,"norm_type") + + @property + def instance(self)->int: + "This property returns the instance" + return self.__instance + + + \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithSubspace.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithSubspace.py new file mode 100644 index 0000000..c118f91 --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/ModularNonBBOBProblemWithSubspace.py @@ -0,0 +1,316 @@ +# Import the libraries +import numpy as np +import ioh +from ioh import wrap_problem +from ioh import ProblemClass +from ioh.iohcpp import MAX,MIN +from typing import List, Union, Tuple, Optional, Callable, Dict +from .ModularMetaData import ModularMetaData +from ioh.iohcpp import RealSolution +from .ModularNonBBOBProblem import (bimodal_function, + multimodal_function, + plateau_function, + ackley, + gramacy_lee, + levy, + rastrigin, + schwefel, + sum_different_powers_function, + forrester, + sphere, + alpine, + styblinski_tank, + salomon) + +''' +This code is defined as some extension to define modular problems from the +BBOB framework. +@Author: + - Ivan Olarte Rodriguez + +''' + +class ModularNonBBOBProblemWithSubspace(ioh.iohcpp.problem.RealSingleObjective): + """ + This class is defined to perform a Single Objective Optimization based by building blocks of + instances 1D problems. + + In this case, the problem is defined as a linear combination of each of the parameters. + """ + + __namespace_dict:dict = {"bimodal":bimodal_function, + "multimodal":multimodal_function, + "plateau":plateau_function, + "ackley":ackley, + "gramacy_lee":gramacy_lee, + "levy":levy, + "rastrigin":rastrigin, + "schwefel":schwefel, + "sum_different_powers":sum_different_powers_function, + "forrester":forrester, + "sphere":sphere, + "alpine":alpine, + "styblinski_tank":styblinski_tank, + "salomon":salomon} + + __idx_space_dict:dict = {1:"bimodal", + 2:"multimodal", + 3:"plateau", + 4:"ackley", + 5:"gramacy_lee", + 6:"levy", + 7:"rastrigin", + 8:"schwefel", + 9:"sum_different_powers", + 10:"forrester", + 11:"sphere", + 12:"alpine", + 13:"styblinski_tank", + 14:"salomon"} + + __optimum_dict:dict = {"bimodal":{'x':5*np.sqrt(3/5), + 'y':bimodal_function(5*np.sqrt(3/5))}, + "multimodal":{'x':0.0, + 'y':multimodal_function(0)}, + "plateau":{'x':0.5+np.sqrt(3/4), + 'y':plateau_function(0.5+np.sqrt(3/4))}, + "ackley":{'x':0.0, 'y':ackley(0.0)}, + "gramacy_lee":{'x':-4.757183, 'y':gramacy_lee(-4.757183)}, + "levy":{'x':0.5, 'y':levy(0.5)}, + "rastrigin":{'x':0.0, 'y':rastrigin(0.0)}, + "schwefel":{'x':420.9687/100, 'y':schwefel(420.9687/100)}, + "sum_different_powers":{'x':0, 'y':sum_different_powers_function(0.0)}, + "forrester":{'x':0.7572*10-5, 'y':forrester(0.7572*10-5)}, + "sphere":{'x':0.0, 'y':sphere(0.0)}, + "alpine":{'x':0.0, 'y':alpine(0.0)}, + "styblinski_tank":{'x':-2.903534, 'y':styblinski_tank(-2.903534)}, + "salomon":{'x':0.0, 'y':salomon(0.0)}} + + __modes:dict = {"average":1,"maximum":2} + + __latent_dimensionality:int = 1 + __instance:int = 0 + + #def __init__(self, name, n_variables, instance, is_minimization, bounds, constraints, optimum): + def __init__(self, + fid:Union[int,str], + n_repetitions:int, + mode:Optional[Union[str,int]]="average")->object: + + r""" + This is the initialiser of the class. This takes the following parameters: + + Args: + ------------- + - fid: The problem identifier "either a name or an identifier between 1 to 10" + - n_repetitions: The times the latent dimension is repeated. + """ + + # Start the function and name as some empty variables + func:Callable = None + func_name:str = None + func_id:int = 0 + + # Get one of the problems + if isinstance(fid,str): + # Check if the string matches one of the names + if fid in [*self.__namespace_dict]: + func = self.__namespace_dict[fid] + func_name = fid + + # Do the inverse mapping of the dictionary + inv_map:dict = {v: k for k, v in self.__idx_space_dict.items()} + # Get the id + func_id = inv_map[fid] + else: + raise AttributeError(f"The fid given is a string that does not match the options in {[*self.__namespace_dict]}", + fid,"fid") + elif isinstance(fid,int): + + # Check if the integer is included in the set + if fid in [*self.__idx_space_dict]: + # Set the id + func_id = fid + # Get the value + func_name = self.__idx_space_dict[fid] + func = self.__namespace_dict[func_name] + + else: + raise AttributeError(f"The fid given is not an integer included in the set: {[*self.__idx_space_dict]}", + "fid",fid) + else: + raise AttributeError(f"The fid given is not an integer or a string", + "fid",fid) + + # Set the mode + self.mode = mode + + + # Extract the optimum information from actual instance + intrinsic_optimum = RealSolution(x=[self.__optimum_dict[func_name]['x']], + y=self.__optimum_dict[func_name]['y']) + + # Define a wrapper + self.__intrinsic_problem:ioh.problem.RealSingleObjective = wrap_problem(function=func, + name=func_name, + dimension=self.__latent_dimensionality, + instance=self.__instance, + optimization_type=MIN, + lb=-5, + ub=5 + ) + + #Extract the constraints and bounds as objects from intrinsic/latent problem + actual_bounds = self.__intrinsic_problem.bounds + actual_constraints:ioh.iohcpp.RealConstraintSet = [] + + + + + + actual_optimum = RealSolution( + x=np.tile(intrinsic_optimum.x,n_repetitions).ravel(), + y=intrinsic_optimum.y + ) + + # Check the minimisation type + if self.__intrinsic_problem.meta_data.optimization_type == ioh.iohcpp.MIN: + isminimisation = True + else: + isminimisation = False + + #Initialize the superclass, being the BBOB class from IOH + super().__init__( "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name,#name + self.__latent_dimensionality*n_repetitions,#n_variables + self.__instance, #instance + isminimisation, #is_minimization + actual_bounds, #bounds + actual_constraints, #constraints + actual_optimum #optimum + ) + + # Set the function id + super().set_id(func_id) + + # Overload the MetaData + self.__meta_data = ModularMetaData(problem_id=self.__intrinsic_problem.meta_data.problem_id, + instance= self.__intrinsic_problem.meta_data.instance, + name= "Modular_Non_BBOB_" + self.__intrinsic_problem.meta_data.name, + n_variables=self.__latent_dimensionality*n_repetitions, + optimization_type=self.__intrinsic_problem.meta_data.optimization_type, + latent_dimensionality=self.__latent_dimensionality + ) + + def evaluate(self, + x:np.ndarray)->float: + r""" + This is an overload of the evaluate function from IOH. This will be computed as + the average of each evaluation of the intrinsic problem. + + Args: + ------- + - x (`np.ndarray`): An array (preferably NumPy) to compute the target + """ + + # Convert the array to Numpy (to be safe) + x_mod:np.ndarray = np.array(x) + + # Reshape the array to evaluate the intrinsic function + x_reshape:np.ndarray = x_mod.reshape((self.meta_data.n_repetitions,self.meta_data.latent_dimensionality)) + + # This is to set in case the mode is set as the average + if self.mode == "average": + average_value:float = x_reshape.mean(axis=None) + + # Compute the intrinsic function + return self.__intrinsic_problem([average_value]) + + # This is to set in case the mode is set as the maximum + elif self.mode == "maximum": + + max_value:float = x_reshape.max(axis=None) + + # Compute the intrinsic function + return self.__intrinsic_problem([max_value]) + + + def create(self, id, iid, dim): + """ + This is defined as some overload function. However, this will not be used and raises a `NotImplementedError` + """ + raise NotImplementedError() + + def reset(self)->None: + """ + Overload of reset function from the super class + """ + # Perform the reset method by following the super class + super().reset() + + # Reset the intrinsic problem + self.__intrinsic_problem.reset() + + def attach_logger_to_intrinsic_problem(self, + logger:ioh.logger.AbstractLogger)->None: + + """ + This function appends a separate data logger in case for the intrinsic problem + """ + + self.__intrinsic_problem.attach_logger(logger) + + def detach_logger_from_intrinsic_problem(self)->None: + """ + This function detaches the logger from the intrinsic problem + """ + return self.__intrinsic_problem.detach_logger() + + + def detach_logger(self)->None: + r""" + This is an overload function from the super class. To be safe, this detaches the logger from this instance and the intrinsic problem instance + """ + + # Use the super_class definition + super().detach_logger() + + self.detach_logger_from_intrinsic_problem() + + # @property + # def intrinsic_problem(self)->ioh.problem.BBOB: + # "This property just returns the intrinsic problem computed by using the BBOB" + # return self.__intrinsic_problem + + @property + def meta_data(self)->ModularMetaData: + "This property explotes the overload of the meta-data" + return self.__meta_data + + @property + def mode(self)->str: + "This property returns the mode of the problem" + return self.__mode + + @mode.setter + def mode(self, mode:Union[str,int])->None: + "This property sets the mode of the problem" + + if isinstance(mode,str): + if mode in [*self.__modes]: + self.__mode = mode + else: + raise AttributeError(f"The mode given is a string that does not match the options in {[*self.__modes]}", + mode,"mode") + + elif isinstance(mode,int): + if mode in [*self.__modes.values()]: + self.__mode = mode + else: + raise AttributeError(f"The mode given is an integer that does not match the options in {[*self.__modes.values()]}", + mode,"mode") + + else: + raise AttributeError(f"The mode given is not an integer or a string", + mode,"mode") + + diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__init__.py b/mylib/lib_BO_torch_repo/IOH_Wrappers/__init__.py new file mode 100644 index 0000000..44e3e8a --- /dev/null +++ b/mylib/lib_BO_torch_repo/IOH_Wrappers/__init__.py @@ -0,0 +1,7 @@ +from .ModularBBOBProblem import ModularBBOBProblem +from .ModularNonBBOBProblem import ModularNonBBOBProblem +from .ModularNonBBOBProblemWithSubspace import ModularNonBBOBProblemWithSubspace +from .ModularNonBBOBProblemWithDistance import ModularNonBBOBProblemWithDistance +from .ModularNonBBOBProblemWithPerturbedDistance import ModularNonBBOBProblemWithPerturbedDistance +from .Generalized_Gallagher_Functions.Generalized_Gallagher_Function_with_repetitions import GeneralizedGallagherFunctionWithRepetitions +from .Generalized_Gallagher_Functions.Generalized_Gallagher_Function import GeneralizedGallagherFunction \ No newline at end of file diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-310.pyc new file mode 100644 index 0000000..321bf3f Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-311.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-311.pyc new file mode 100644 index 0000000..3994539 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-312.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-312.pyc new file mode 100644 index 0000000..4637a9c Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-312.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-39.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-39.pyc new file mode 100644 index 0000000..864a6f6 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularBBOBProblem.cpython-39.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularLogInfo.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularLogInfo.cpython-310.pyc new file mode 100644 index 0000000..1c39ea9 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularLogInfo.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-310.pyc new file mode 100644 index 0000000..f82bb2a Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-311.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-311.pyc new file mode 100644 index 0000000..365a0d3 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-39.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-39.pyc new file mode 100644 index 0000000..c3aaeef Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularMetaData.cpython-39.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblem.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblem.cpython-310.pyc new file mode 100644 index 0000000..5abadb8 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblem.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithDistance.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithDistance.cpython-310.pyc new file mode 100644 index 0000000..321e828 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithDistance.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithPerturbedDistance.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithPerturbedDistance.cpython-310.pyc new file mode 100644 index 0000000..68923e5 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithPerturbedDistance.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithSubspace.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithSubspace.cpython-310.pyc new file mode 100644 index 0000000..feb5661 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/ModularNonBBOBProblemWithSubspace.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-310.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..a2ba443 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-311.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1b6f45f Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-311.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-312.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..60feaa2 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-312.pyc differ diff --git a/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-39.pyc b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..85090f8 Binary files /dev/null and b/mylib/lib_BO_torch_repo/IOH_Wrappers/__pycache__/__init__.cpython-39.pyc differ diff --git a/mylib/lib_REMBO/HesBO/BLOSSOM/README.md b/mylib/lib_REMBO/HesBO/BLOSSOM/README.md new file mode 100644 index 0000000..a6d1499 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/BLOSSOM/README.md @@ -0,0 +1,8 @@ +# HeSBO embedding on BLOSSOM +One first needs to install the BLOSSOM package on their machine. To do so, we refer the readers to the [BLOSSOM github page][1]. After installing the package, one simply can use the below line to run the BLOSSOM using the HeSBO embedding. +```bash +python blossom_run.py [test_function] [num_of_steps] [low_dim] [high_dim] [num_of_initial_sample] [job_id] [noise_variance] +``` +Please note to change the line 9 in the blossom_run.py file and give the path of the directory in which the results would be collected. + +[1]: https://github.com/markm541374/gpbo diff --git a/mylib/lib_REMBO/HesBO/BLOSSOM/blossom_run.py b/mylib/lib_REMBO/HesBO/BLOSSOM/blossom_run.py new file mode 100644 index 0000000..f20ebd7 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/BLOSSOM/blossom_run.py @@ -0,0 +1,46 @@ +import numpy as np +import time +import logging +import pickle +import sys +import gpbo +from embd_functions import * + +path='/home/u22/aminnayebi/BLOSSOM/blossom_results' +test_func = sys.argv[1] +total_itr = int(sys.argv[2]) +low_dim = int(sys.argv[3]) +high_dim = int(sys.argv[4]) +init_n = int(sys.argv[5]) +rep = int(sys.argv[6]) +if len(sys.argv)<8: + noise_var=0 +else: + noise_var=int(sys.argv[7]) + +s=noise_var +act_var=np.arange(high_dim) +high_to_low=np.random.choice(range(low_dim), high_dim) +sign = np.random.choice([-1, 1], high_dim) +bx_size=1 + +if test_func=='Branin': + f = Branin(act_var, high_to_low, sign, bx_size, noise_var=noise_var) +elif test_func=='MNIST': + f = MNIST(act_var, high_to_low, sign, bx_size, noise_var=noise_var) +elif test_func=='Hartmann6': + f = Hartmann6(act_var, high_to_low, sign, bx_size, noise_var=noise_var) +elif test_func=='Rosenbrock': + f = Rosenbrock(act_var, high_to_low, sign, bx_size, noise_var=noise_var) +elif test_func=='StybTang': + f = StybTang(act_var, high_to_low, sign, bx_size, noise_var=noise_var) + +file_name=test_func+'_blossom_d'+str(low_dim)+'_D'+str(high_dim)+'_rep_'+str(rep)+'.csv' +C=gpbo.core.config.eimledefault(f, low_dim, total_itr, s, path, file_name) + +if noise_var>0: + file_name_params = 'blossom_results/param_'+ test_func+'_blossom_d'+str(low_dim)+'_D'+str(high_dim)+'_rep_'+str(rep) + pickle.dump([high_to_low, sign], open(file_name_params, 'wb')) + +out = gpbo.search(C) +print(out) diff --git a/mylib/lib_REMBO/HesBO/BLOSSOM/embd_functions.py b/mylib/lib_REMBO/HesBO/BLOSSOM/embd_functions.py new file mode 100644 index 0000000..acd7d5e --- /dev/null +++ b/mylib/lib_REMBO/HesBO/BLOSSOM/embd_functions.py @@ -0,0 +1,199 @@ +import math +import numpy as np +# import torch +# from BOCK_benchmarks.mnist_weight import mnist_weight +# All functions are minimization problems + +def back_projection(low_obs, high_to_low, sign, bx_size): + if len(low_obs.shape) == 1: + low_obs = low_obs.reshape((1, low_obs.shape[0])) + n = low_obs.shape[0] + high_dim = high_to_low.shape[0] + low_dim = low_obs.shape[1] + high_obs = np.zeros((n, high_dim)) + scale = 1 + for i in range(high_dim): + high_obs[:, i] = sign[i] * low_obs[:, high_to_low[i]] * scale + for i in range(n): + for j in range(high_dim): + if high_obs[i][j] > bx_size: + high_obs[i][j] = bx_size + elif high_obs[i][j] < -bx_size: + high_obs[i][j] = -bx_size + return high_obs + +class Branin(object): + def __init__(self, act_var, high_to_low, sign, bx_size, noise_var=0): + self.range=np.array([[-5,10], + [0,15]]) + self.high_to_low=high_to_low + self.sign=sign + self.bx_size=bx_size + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy,self.high_to_low,self.sign,self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def __call__(self,x,**ev): + scaled_x=self.scale_domain(x) + # Calculating the output + f = [[0]] + f[0] = [-((i[1] - (5.1 / (4 * math.pi ** 2)) * i[0] ** 2 + i[0] * 5 / math.pi - 6) ** 2 + 10 * ( + 1 - 1 / (8 * math.pi)) * np.cos(i[0]) + 10) for i in scaled_x] + f = np.transpose(f) + return -np.squeeze(f) + np.random.normal(0,self.var), 1, dict() + +class Hartmann6(object): + def __init__(self, act_var, high_to_low, sign, bx_size, noise_var=0): + self.range = np.array([[0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1]]) + self.act_var = act_var + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def __call__(self,x,**ev): + # Calculating the output + #Created on 08.09.2016 + # @author: Stefan Falkner + alpha = [1.00, 1.20, 3.00, 3.20] + A = np.array([[10.00, 3.00, 17.00, 3.50, 1.70, 8.00], + [0.05, 10.00, 17.00, 0.10, 8.00, 14.00], + [3.00, 3.50, 1.70, 10.00, 17.00, 8.00], + [17.00, 8.00, 0.05, 10.00, 0.10, 14.00]]) + P = 0.0001 * np.array([[1312, 1696, 5569, 124, 8283, 5886], + [2329, 4135, 8307, 3736, 1004, 9991], + [2348, 1451, 3522, 2883, 3047, 6650], + [4047, 8828, 8732, 5743, 1091, 381]]) + scaled_x = self.scale_domain(x) + n=len(scaled_x) + external_sum = np.zeros((n,1)) + for r in range(n): + for i in range(4): + internal_sum = 0 + for j in range(6): + internal_sum = internal_sum + A[i, j] * (scaled_x[r, self.act_var[j]] - P[i, j]) ** 2 + external_sum[r] = external_sum[r] + alpha[i] * np.exp(-internal_sum) + return -np.squeeze(external_sum) + np.random.normal(0,self.var), 1, dict() + +class Rosenbrock(object): + def __init__(self, act_var, high_to_low, sign, bx_size, noise_var=0): + self.range = np.array([[-2, 2], + [-2, 2]]) + # self.range=np.array([[-2,1], + # [1,2]]) + self.act_var = act_var + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + self.var = noise_var + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def __call__(self, x, **ev): + # Calculating the output + scaled_x = self.scale_domain(x) + f = [[0]] + f[0] = [-(math.pow(1 - i[self.act_var[0]], 2) + 100 * math.pow( + i[self.act_var[1]] - math.pow(i[self.act_var[0]], 2), 2)) for i in scaled_x] + f = np.transpose(f) + return -np.squeeze(f) + np.random.normal(0,self.var), 1, dict() + +class StybTang(object): + def __init__(self, act_var, high_to_low, sign, bx_size, noise_var=0): + D = len(act_var) + a = np.ones((D, 2)) + a = a * 5 + a[:, 0] = a[:, 0] * -1 + self.range = a + self.high_to_low=high_to_low + self.sign=sign + self.bx_size=bx_size + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy,self.high_to_low,self.sign,self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def __call__(self,x,**ev): + scaled_x=self.scale_domain(x) + # Calculating the output + f = [-0.5 * np.sum(np.power(scaled_x, 4) - 16 * np.power(scaled_x, 2) + 5 * scaled_x, axis=1)] + f = np.transpose(f) + return -np.squeeze(f) + np.random.normal(0,self.var), 1, dict() + +class MNIST(object): + def __init__(self, act_var, high_to_low, sign, bx_size): + D = len(act_var) + a = np.ones((D, 2)) + a = a * 1 + a[:, 0] = a[:, 0] * -1 + self.range = a + self.act_var = act_var + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def __call__(self,x, **ev): + scaled_x = self.scale_domain(x) + if len(scaled_x.shape) == 1: + scaled_x = scaled_x.reshape((1, scaled_x.shape[0])) + n = len(scaled_x) + res = np.zeros((n, 1)) + for i in range(n): + x = scaled_x[i] + x = torch.from_numpy(x).type(torch.FloatTensor) + res[i] = -mnist_weight(x) + return -res, 1, dict() + diff --git a/mylib/lib_REMBO/HesBO/Branin_D100_d4.jpg b/mylib/lib_REMBO/HesBO/Branin_D100_d4.jpg new file mode 100644 index 0000000..d4a94ab Binary files /dev/null and b/mylib/lib_REMBO/HesBO/Branin_D100_d4.jpg differ diff --git a/mylib/lib_REMBO/HesBO/KG/README.md b/mylib/lib_REMBO/HesBO/KG/README.md new file mode 100644 index 0000000..f5e1cc7 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/KG/README.md @@ -0,0 +1,7 @@ +# HeSBO embedding on KG +To run the knowledge gradient based algorithm with HeSBO embedding, we first need to install the Corenell-MOE package on our machine. To do so, we refer the readers to the [Cornell-MOE github page][1]. After installing the package, one simply can use the below line to run the KG using the HeSBO embedding. +```bash +python moe_run.py [test_function] [KG] [num_of_steps] [low_dim] [high_dim] [num_of_initial_sample] [job_id] [noise_variance] +``` + +[1]: https://github.com/wujian16/Cornell-MOE \ No newline at end of file diff --git a/mylib/lib_REMBO/HesBO/KG/embd_functions.py b/mylib/lib_REMBO/HesBO/KG/embd_functions.py new file mode 100644 index 0000000..e273974 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/KG/embd_functions.py @@ -0,0 +1,265 @@ +import math +import numpy as np +from builtins import object +# import torch +# from BOCK_benchmarks.mnist_weight import mnist_weight + +#Changes 1- Noises are added + +# All functions are minimization problems + +def back_projection(low_obs, high_to_low, sign, bx_size): + if len(low_obs.shape) == 1: + low_obs = low_obs.reshape((1, low_obs.shape[0])) + n = low_obs.shape[0] + high_dim = high_to_low.shape[0] + low_dim = low_obs.shape[1] + high_obs = np.zeros((n, high_dim)) + scale = 1 + for i in range(high_dim): + high_obs[:, i] = sign[i] * low_obs[:, high_to_low[i]] * scale + for i in range(n): + for j in range(high_dim): + if high_obs[i][j] > bx_size: + high_obs[i][j] = bx_size + elif high_obs[i][j] < -bx_size: + high_obs[i][j] = -bx_size + return high_obs + + +class Branin(object): + def __init__(self, act_var, low_dim, high_to_low, sign, bx_size, noise_var=0): + self.range = np.array([[-5, 10], + [0, 15]]) + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0.397887 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + scaled_x = self.scale_domain(x) + n=len(scaled_x) + # Calculating the output + f = [[0]] + f[0] = [-((i[1] - (5.1 / (4 * math.pi ** 2)) * i[0] ** 2 + i[0] * 5 / math.pi - 6) ** 2 + 10 * ( + 1 - 1 / (8 * math.pi)) * np.cos(i[0]) + 10) for i in scaled_x] + f = np.transpose(f) + return -f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + + +class Hartmann6(object): + def __init__(self, act_var, low_dim, high_to_low, sign, bx_size, noise_var=0): + self.range = np.array([[0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1]]) + self.act_var = act_var + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = -3.32237 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + # Calculating the output + # Created on 08.09.2016 + # @author: Stefan Falkner + alpha = [1.00, 1.20, 3.00, 3.20] + A = np.array([[10.00, 3.00, 17.00, 3.50, 1.70, 8.00], + [0.05, 10.00, 17.00, 0.10, 8.00, 14.00], + [3.00, 3.50, 1.70, 10.00, 17.00, 8.00], + [17.00, 8.00, 0.05, 10.00, 0.10, 14.00]]) + P = 0.0001 * np.array([[1312, 1696, 5569, 124, 8283, 5886], + [2329, 4135, 8307, 3736, 1004, 9991], + [2348, 1451, 3522, 2883, 3047, 6650], + [4047, 8828, 8732, 5743, 1091, 381]]) + scaled_x = self.scale_domain(x) + n = len(scaled_x) + external_sum = np.zeros((n, 1)) + for r in range(n): + for i in range(4): + internal_sum = 0 + for j in range(6): + internal_sum = internal_sum + A[i, j] * (scaled_x[r, self.act_var[j]] - P[i, j]) ** 2 + external_sum[r] = external_sum[r] + alpha[i] * np.exp(-internal_sum) + return -external_sum + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class Rosenbrock(object): + def __init__(self, act_var, low_dim, high_to_low, sign, bx_size, noise_var=0): + self.range = np.array([[-2, 2], + [-2, 2]]) + # self.range=np.array([[-2,1], + # [1,2]]) + self.act_var = act_var + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + # Calculating the output + scaled_x = self.scale_domain(x) + n=len(scaled_x) + f = [[0]] + f[0] = [-(math.pow(1 - i[self.act_var[0]], 2) + 100 * math.pow( + i[self.act_var[1]] - math.pow(i[self.act_var[0]], 2), 2)) for i in scaled_x] + f = np.transpose(f) + return -f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class StybTang(object): + def __init__(self, act_var, low_dim, high_to_low, sign, bx_size, noise_var=0): + D = len(act_var) + a = np.ones((D, 2)) + a = a * 5 + a[:, 0] = a[:, 0] * -1 + self.range = a + self.act_var = act_var + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + # Calculating the output + scaled_x = self.scale_domain(x) + f = [-0.5 * np.sum(np.power(scaled_x, 4) - 16 * np.power(scaled_x, 2) + 5 * scaled_x, axis=1)] + f = np.transpose(f) + return -f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class MNIST(object): + def __init__(self, act_var, low_dim, high_to_low, sign, bx_size): + D = len(act_var) + a = np.ones((D, 2)) + a = a * 1 + a[:, 0] = a[:, 0] * -1 + self.range = a + self.act_var = act_var + self.high_to_low = high_to_low + self.sign = sign + self.bx_size = bx_size + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + x_copy = back_projection(x_copy, self.high_to_low, self.sign, self.bx_size) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self,x): + scaled_x = self.scale_domain(x) + if len(scaled_x.shape) == 1: + scaled_x = scaled_x.reshape((1, scaled_x.shape[0])) + n = len(scaled_x) + res = np.zeros((n, 1)) + for i in range(n): + x = scaled_x[i] + x = torch.from_numpy(x).type(torch.FloatTensor) + res[i] = -mnist_weight(x) + return -res + + def evaluate(self, x): + return self.evaluate_true(x) diff --git a/mylib/lib_REMBO/HesBO/KG/examples/bayesian_optimization.py b/mylib/lib_REMBO/HesBO/KG/examples/bayesian_optimization.py new file mode 100644 index 0000000..40ffa00 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/KG/examples/bayesian_optimization.py @@ -0,0 +1,88 @@ +import numpy + +from moe.optimal_learning.python.cpp_wrappers.expected_improvement import ExpectedImprovement as cppExpectedImprovement +from moe.optimal_learning.python.cpp_wrappers.expected_improvement import multistart_expected_improvement_optimization +from moe.optimal_learning.python.cpp_wrappers.expected_improvement_mcmc import multistart_expected_improvement_mcmc_optimization +from moe.optimal_learning.python.cpp_wrappers.expected_improvement_mcmc import ExpectedImprovementMCMC as cppExpectedImprovementMCMC + +from moe.optimal_learning.python.cpp_wrappers.knowledge_gradient_mcmc import KnowledgeGradientMCMC as cppKnowledgeGradientMCMC +from moe.optimal_learning.python.cpp_wrappers.knowledge_gradient_mcmc import multistart_knowledge_gradient_mcmc_optimization + +from moe.optimal_learning.python.cpp_wrappers.optimization import GradientDescentOptimizer as cppGradientDescentOptimizer + +def gen_sample_from_qei(cpp_gp, cpp_search_domain, sgd_params, num_to_sample, num_mc=1e4, lhc_itr=2e4): + """ + :param cpp_gp: trained cpp version of GaussianProcess model + :param cpp_search_domain: cpp version of TensorProductDomain + :param sgd_params: GradientDescentParameters + :param num_to_sample: number of points to sample for the next iteration + :param num_mc: number of Monte Carlo iterations + :param lhc_itr: number of points used in latin hypercube search + :return: (points to sample next, expected improvement at this set of points) + """ + cpp_ei_evaluator = cppExpectedImprovement(gaussian_process=cpp_gp, num_mc_iterations=int(num_mc)) + #python_ei_evaluator = pythonExpectedImprovement(gaussian_process=cpp_gp, num_mc_iterations=int(num_mc)) + #optimizer = pythonGradientDescentOptimizer(cpp_search_domain, python_ei_evaluator, sgd_params, int(lhc_itr)) + optimizer = cppGradientDescentOptimizer(cpp_search_domain, cpp_ei_evaluator, sgd_params, int(lhc_itr)) + points_to_sample_list = [] + ei_list = [] + points_to_sample_list.append(multistart_expected_improvement_optimization(optimizer, None, num_to_sample, + use_gpu=False, which_gpu=0, + max_num_threads=1)) + + cpp_ei_evaluator.set_current_point(points_to_sample_list[0]) + ei_list.append(cpp_ei_evaluator.compute_expected_improvement()) + return points_to_sample_list[numpy.argmax(ei_list)], numpy.amax(ei_list) + +def gen_sample_from_qei_mcmc(cpp_gp_mcmc, cpp_search_domain, sgd_params, num_to_sample, num_mc=1e4, lhc_itr=2e4): + """ + :param cpp_gp_mcmc: trained cpp version of GaussianProcess MCMC model + :param cpp_search_domain: cpp version of TensorProductDomain + :param sgd_params: GradientDescentParameters + :param num_to_sample: number of points to sample for the next iteration + :param num_mc: number of Monte Carlo iterations + :param lhc_itr: number of points used in latin hypercube search + :return: (points to sample next, expected improvement at this set of points) + """ + cpp_ei_evaluator = cppExpectedImprovementMCMC(gaussian_process_mcmc = cpp_gp_mcmc, + num_to_sample = num_to_sample, num_mc_iterations=int(num_mc)) + optimizer = cppGradientDescentOptimizer(cpp_search_domain, cpp_ei_evaluator, sgd_params, int(lhc_itr)) + points_to_sample_list = [] + ei_list = [] + + points_to_sample_list.append(multistart_expected_improvement_mcmc_optimization(optimizer, None, + num_to_sample=num_to_sample, + max_num_threads=1)) + cpp_ei_evaluator.set_current_point(points_to_sample_list[0]) + ei_list.append(cpp_ei_evaluator.compute_objective_function()) + return points_to_sample_list[numpy.argmax(ei_list)], numpy.amax(ei_list) + +def gen_sample_from_qkg_mcmc(cpp_gp_mcmc, cpp_gp_list, inner_optimizer, cpp_search_domain, num_fidelity, + discrete_pts_list, sgd_params, num_to_sample, num_mc=10, lhc_itr=1e3): + """ + :param cpp_gp_mcmc: trained cpp version of GaussianProcess MCMC model + :param cpp_gp_list: + :param inner_optimizer: + :param cpp_search_domain: cpp version of TensorProductDomain + :param num_fidelity: number of fidelity control parameters + :param discrete_pts_list: + :param sgd_params: GradientDescentParameters + :param num_mc: number of Monte Carlo iterations + :param lhc_itr: number of points used in latin hypercube search + :return: (points to sample next, expected improvement at this set of points) + """ + cpp_kg_evaluator = cppKnowledgeGradientMCMC(gaussian_process_mcmc = cpp_gp_mcmc, gaussian_process_list=cpp_gp_list, + num_fidelity = num_fidelity, inner_optimizer = inner_optimizer, discrete_pts_list=discrete_pts_list, + num_to_sample = num_to_sample, num_mc_iterations=int(num_mc)) + optimizer = cppGradientDescentOptimizer(cpp_search_domain, cpp_kg_evaluator, sgd_params, int(lhc_itr)) + points_to_sample_list = [] + kg_list = [] + + points_to_sample_list.append(multistart_knowledge_gradient_mcmc_optimization(optimizer, inner_optimizer, None, discrete_pts_list, + num_to_sample=num_to_sample, + num_pts=discrete_pts_list[0].shape[0], + max_num_threads=1)) + + cpp_kg_evaluator.set_current_point(points_to_sample_list[0]) + kg_list.append(cpp_kg_evaluator.compute_objective_function()) + return points_to_sample_list[numpy.argmax(kg_list)], numpy.amax(kg_list) \ No newline at end of file diff --git a/mylib/lib_REMBO/HesBO/KG/examples/functions_vanilla.py b/mylib/lib_REMBO/HesBO/KG/examples/functions_vanilla.py new file mode 100644 index 0000000..d38f300 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/KG/examples/functions_vanilla.py @@ -0,0 +1,232 @@ +import math +import numpy as np +from builtins import object +# import torch +# from BOCK_benchmarks.mnist_weight import mnist_weight + +#Changes 1- Noises are added + +# All functions are minimization problems + + +class Branin(object): + def __init__(self, act_var, low_dim, bx_size, noise_var=0): + self.range = np.array([[-5, 10], + [0, 15]]) + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0.397887 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + scaled_x = self.scale_domain(x) + n=len(scaled_x) + # Calculating the output + f = [[0]] + f[0] = [-((i[1] - (5.1 / (4 * math.pi ** 2)) * i[0] ** 2 + i[0] * 5 / math.pi - 6) ** 2 + 10 * ( + 1 - 1 / (8 * math.pi)) * np.cos(i[0]) + 10) for i in scaled_x] + f = np.transpose(f) + return -f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + + +class Hartmann6(object): + def __init__(self, act_var, low_dim, bx_size, noise_var=0): + self.range = np.array([[0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1]]) + self.act_var = act_var + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = -3.32237 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + # Calculating the output + # Created on 08.09.2016 + # @author: Stefan Falkner + alpha = [1.00, 1.20, 3.00, 3.20] + A = np.array([[10.00, 3.00, 17.00, 3.50, 1.70, 8.00], + [0.05, 10.00, 17.00, 0.10, 8.00, 14.00], + [3.00, 3.50, 1.70, 10.00, 17.00, 8.00], + [17.00, 8.00, 0.05, 10.00, 0.10, 14.00]]) + P = 0.0001 * np.array([[1312, 1696, 5569, 124, 8283, 5886], + [2329, 4135, 8307, 3736, 1004, 9991], + [2348, 1451, 3522, 2883, 3047, 6650], + [4047, 8828, 8732, 5743, 1091, 381]]) + scaled_x = self.scale_domain(x) + n = len(scaled_x) + external_sum = np.zeros((n, 1)) + for r in range(n): + for i in range(4): + internal_sum = 0 + for j in range(6): + internal_sum = internal_sum + A[i, j] * (scaled_x[r, self.act_var[j]] - P[i, j]) ** 2 + external_sum[r] = external_sum[r] + alpha[i] * np.exp(-internal_sum) + return -external_sum + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class Rosenbrock(object): + def __init__(self, act_var, low_dim, bx_size, noise_var=0): + self.range = np.array([[-2, 2], + [-2, 2]]) + # self.range=np.array([[-2,1], + # [1,2]]) + self.act_var = act_var + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + # Calculating the output + scaled_x = self.scale_domain(x) + n=len(scaled_x) + f = [[0]] + f[0] = [-(math.pow(1 - i[self.act_var[0]], 2) + 100 * math.pow( + i[self.act_var[1]] - math.pow(i[self.act_var[0]], 2), 2)) for i in scaled_x] + f = np.transpose(f) + return -f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class StybTang(object): + def __init__(self, act_var, low_dim, bx_size, noise_var=0): + D = len(act_var) + a = np.ones((D, 2)) + a = a * 5 + a[:, 0] = a[:, 0] * -1 + self.range = a + self.act_var = act_var + self.bx_size = bx_size + self.var = noise_var + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self, x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self, x): + # Calculating the output + scaled_x = self.scale_domain(x) + f = [-0.5 * np.sum(np.power(scaled_x, 4) - 16 * np.power(scaled_x, 2) + 5 * scaled_x, axis=1)] + f = np.transpose(f) + return -f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class MNIST(object): + def __init__(self, act_var, low_dim, bx_size): + D = len(act_var) + a = np.ones((D, 2)) + a = a * 1 + a[:, 0] = a[:, 0] * -1 + self.range = a + self.act_var = act_var + self.bx_size = bx_size + # MOE specialization + self._dim = low_dim + self._search_domain = np.repeat([[-bx_size, bx_size]], self._dim, axis=0) + self._sample_var = 0.0 + self._min_value = 0 + self._observations = [] # numpy.arange(self._dim) + self._num_fidelity = 0 + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self,x): + scaled_x = self.scale_domain(x) + if len(scaled_x.shape) == 1: + scaled_x = scaled_x.reshape((1, scaled_x.shape[0])) + n = len(scaled_x) + res = np.zeros((n, 1)) + for i in range(n): + x = scaled_x[i] + x = torch.from_numpy(x).type(torch.FloatTensor) + res[i] = -mnist_weight(x) + return -res + + def evaluate(self, x): + return self.evaluate_true(x) diff --git a/mylib/lib_REMBO/HesBO/KG/moe_run.py b/mylib/lib_REMBO/HesBO/KG/moe_run.py new file mode 100644 index 0000000..fdeb51f --- /dev/null +++ b/mylib/lib_REMBO/HesBO/KG/moe_run.py @@ -0,0 +1,298 @@ +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division +from builtins import str +from builtins import range +from past.utils import old_div +import numpy as np +import os, sys +import time +from embd_functions import * +import pickle + +from moe.optimal_learning.python.cpp_wrappers.domain import TensorProductDomain as cppTensorProductDomain +from moe.optimal_learning.python.cpp_wrappers.knowledge_gradient_mcmc import PosteriorMeanMCMC +from moe.optimal_learning.python.cpp_wrappers.log_likelihood_mcmc import GaussianProcessLogLikelihoodMCMC as cppGaussianProcessLogLikelihoodMCMC +from moe.optimal_learning.python.cpp_wrappers.optimization import GradientDescentParameters as cppGradientDescentParameters +from moe.optimal_learning.python.cpp_wrappers.optimization import GradientDescentOptimizer as cppGradientDescentOptimizer +from moe.optimal_learning.python.cpp_wrappers.knowledge_gradient import posterior_mean_optimization, PosteriorMean + +from moe.optimal_learning.python.data_containers import HistoricalData, SamplePoint +from moe.optimal_learning.python.geometry_utils import ClosedInterval +from moe.optimal_learning.python.repeated_domain import RepeatedDomain +from moe.optimal_learning.python.default_priors import DefaultPrior + +from moe.optimal_learning.python.python_version.domain import TensorProductDomain as pythonTensorProductDomain +from moe.optimal_learning.python.python_version.optimization import GradientDescentParameters as pyGradientDescentParameters +from moe.optimal_learning.python.python_version.optimization import GradientDescentOptimizer as pyGradientDescentOptimizer +from moe.optimal_learning.python.python_version.optimization import multistart_optimize as multistart_optimize + +from examples import bayesian_optimization + +# arguments for calling this script: +# python main.py [obj_func_name] [method_name] [num_to_sample] [job_id] +# example: python main.py Branin KG 4 1 +# you can define your own obj_function and then just change the objective_func object below, and run this script. + +argv = sys.argv[1:] +obj_func_name = str(argv[0]) +method = str(argv[1]) +num_func_eval = int(argv[2]) +low_dim = int(argv[3]) +high_dim = int(argv[4]) +initial_n = int(argv[5]) +job_id = int(argv[6]) +if len(argv)<8: + noise_var=0 + noisy = False +else: + noise_var=int(argv[7]) + noisy = True + +# constants +num_opt_start=12 +num_to_sample = 1 +num_iteration = int(old_div(num_func_eval, num_to_sample)) + 1 +act_var=np.arange(high_dim) +high_to_low=np.random.choice(range(low_dim), high_dim) +sign = np.random.choice([-1, 1], high_dim) +bx_size=1 +file_name='moe_results/'+obj_func_name+'_MOE_d'+str(low_dim)+'_D'+str(high_dim)+'_init_'+str(initial_n)+'_rep_'+str(job_id) +data_str = '' + +obj_func_dict = {'Branin': Branin(act_var, low_dim, high_to_low, sign, bx_size, noise_var= noise_var), + 'Rosenbrock': Rosenbrock(act_var, low_dim, high_to_low, sign, bx_size, noise_var= noise_var), + 'Hartmann6': Hartmann6(act_var, low_dim, high_to_low, sign, bx_size, noise_var= noise_var), + 'StybTang': StybTang(act_var, low_dim, high_to_low, sign, bx_size, noise_var= noise_var), + 'MNIST': MNIST(act_var, low_dim, high_to_low, sign, bx_size)} + +objective_func = obj_func_dict[obj_func_name] +dim = int(objective_func._dim) +num_initial_points = initial_n + +num_fidelity = objective_func._num_fidelity + +inner_search_domain = pythonTensorProductDomain([ClosedInterval(objective_func._search_domain[i, 0], objective_func._search_domain[i, 1]) + for i in range(objective_func._search_domain.shape[0]-num_fidelity)]) +cpp_search_domain = cppTensorProductDomain([ClosedInterval(bound[0], bound[1]) for bound in objective_func._search_domain]) +cpp_inner_search_domain = cppTensorProductDomain([ClosedInterval(objective_func._search_domain[i, 0], objective_func._search_domain[i, 1]) + for i in range(objective_func._search_domain.shape[0]-num_fidelity)]) + +# get the initial data +init_pts = np.zeros((initial_n, objective_func._dim)) +init_pts[:, :objective_func._dim-objective_func._num_fidelity] = inner_search_domain.generate_uniform_random_points_in_domain(initial_n) +for pt in init_pts: + pt[objective_func._dim-objective_func._num_fidelity:] = np.ones(objective_func._num_fidelity) + +# observe +derivatives = objective_func._observations +observations = [0] + [i+1 for i in derivatives] +init_pts_value = np.array([objective_func.evaluate(pt) for pt in init_pts])#[:, observations] +true_value_init = np.array([objective_func.evaluate_true(pt) for pt in init_pts])#[:, observations] + +# Collecting Data +s_suggest=np.array(init_pts) +f_s_suggest=np.array(init_pts_value).reshape(initial_n, 1) +s_recommend=np.array(init_pts) +f_s_recommend=np.array(true_value_init).reshape(initial_n, 1) +elapsed=np.zeros([1, num_iteration + initial_n]) + +init_data = HistoricalData(dim = objective_func._dim, num_derivatives = len(derivatives)) +init_data.append_sample_points([SamplePoint(pt, [init_pts_value[num, i] for i in observations], + objective_func._sample_var) for num, pt in enumerate(init_pts)]) + +# initialize the model +prior = DefaultPrior(1+dim+len(observations), len(observations)) + +# noisy = False means the underlying function being optimized is noise-free +cpp_gp_loglikelihood = cppGaussianProcessLogLikelihoodMCMC(historical_data = init_data, + derivatives = derivatives, + prior = prior, + chain_length = 1000, + burnin_steps = 2000, + n_hypers = 2 ** 4, + noisy = noisy) +cpp_gp_loglikelihood.train() + +py_sgd_params_ps = pyGradientDescentParameters(max_num_steps=1000, + max_num_restarts=3, + num_steps_averaged=15, + gamma=0.7, + pre_mult=1.0, + max_relative_change=0.02, + tolerance=1.0e-10) + +cpp_sgd_params_ps = cppGradientDescentParameters(num_multistarts=1, + max_num_steps=6, + max_num_restarts=1, + num_steps_averaged=3, + gamma=0.0, + pre_mult=1.0, + max_relative_change=0.1, + tolerance=1.0e-10) + +cpp_sgd_params_kg = cppGradientDescentParameters(num_multistarts=200, + max_num_steps=50, + max_num_restarts=2, + num_steps_averaged=4, + gamma=0.7, + pre_mult=1.0, + max_relative_change=0.5, + tolerance=1.0e-10) + +# minimum of the mean surface +eval_pts = inner_search_domain.generate_uniform_random_points_in_domain(int(1e3)) +eval_pts = np.reshape(np.append(eval_pts, (cpp_gp_loglikelihood.get_historical_data_copy()).points_sampled[:, :(cpp_gp_loglikelihood.dim-objective_func._num_fidelity)]), + (eval_pts.shape[0] + cpp_gp_loglikelihood._num_sampled, + cpp_gp_loglikelihood.dim-objective_func._num_fidelity)) + +test = np.zeros(eval_pts.shape[0]) +ps = PosteriorMeanMCMC(cpp_gp_loglikelihood.models, num_fidelity) +for i, pt in enumerate(eval_pts): + ps.set_current_point(pt.reshape((1, cpp_gp_loglikelihood.dim-objective_func._num_fidelity))) + test[i] = -ps.compute_objective_function() +report_point = eval_pts[np.argmin(test)].reshape((1, cpp_gp_loglikelihood.dim-objective_func._num_fidelity)) + +py_repeated_search_domain = RepeatedDomain(num_repeats = 1, domain = inner_search_domain) +ps_mean_opt = pyGradientDescentOptimizer(py_repeated_search_domain, ps, py_sgd_params_ps) +report_point = multistart_optimize(ps_mean_opt, report_point, num_multistarts = 1)[0] +report_point = report_point.ravel() +report_point = np.concatenate((report_point, np.ones(objective_func._num_fidelity))) + +print("best so far in the initial data {0}".format(true_value_init[np.argmin(true_value_init[:,0])][0])) +capital_so_far = 0. +start=time.time() +for n in range(num_iteration): + print(method + ", {0}th job, {1}th iteration, func={2}, q={3}".format( + job_id, n, obj_func_name, num_to_sample + )) + time1 = time.time() + if method == 'KG': + discrete_pts_list = [] + + discrete, _ = bayesian_optimization.gen_sample_from_qei_mcmc(cpp_gp_loglikelihood._gaussian_process_mcmc, cpp_search_domain, + cpp_sgd_params_kg, 10, num_mc=2 ** 10) + for i, cpp_gp in enumerate(cpp_gp_loglikelihood.models): + discrete_pts_optima = np.array(discrete) + + eval_pts = inner_search_domain.generate_uniform_random_points_in_domain(int(1e3)) + eval_pts = np.reshape(np.append(eval_pts, + (cpp_gp.get_historical_data_copy()).points_sampled[:, :(cpp_gp_loglikelihood.dim-objective_func._num_fidelity)]), + (eval_pts.shape[0] + cpp_gp.num_sampled, cpp_gp.dim-objective_func._num_fidelity)) + + test = np.zeros(eval_pts.shape[0]) + ps_evaluator = PosteriorMean(cpp_gp, num_fidelity) + for i, pt in enumerate(eval_pts): + ps_evaluator.set_current_point(pt.reshape((1, cpp_gp_loglikelihood.dim-objective_func._num_fidelity))) + test[i] = -ps_evaluator.compute_objective_function() + + initial_point = eval_pts[np.argmin(test)] + + ps_sgd_optimizer = cppGradientDescentOptimizer(cpp_inner_search_domain, ps_evaluator, cpp_sgd_params_ps) + report_point = posterior_mean_optimization(ps_sgd_optimizer, initial_guess = initial_point, max_num_threads = 4) + + ps_evaluator.set_current_point(report_point.reshape((1, cpp_gp_loglikelihood.dim-objective_func._num_fidelity))) + if -ps_evaluator.compute_objective_function() > np.min(test): + report_point = initial_point + + discrete_pts_optima = np.reshape(np.append(discrete_pts_optima, report_point), + (discrete_pts_optima.shape[0] + 1, cpp_gp.dim-objective_func._num_fidelity)) + discrete_pts_list.append(discrete_pts_optima) + + ps_evaluator = PosteriorMean(cpp_gp_loglikelihood.models[0], num_fidelity) + ps_sgd_optimizer = cppGradientDescentOptimizer(cpp_inner_search_domain, ps_evaluator, cpp_sgd_params_ps) + # KG method + next_points, voi = bayesian_optimization.gen_sample_from_qkg_mcmc(cpp_gp_loglikelihood._gaussian_process_mcmc, cpp_gp_loglikelihood.models, + ps_sgd_optimizer, cpp_search_domain, num_fidelity, discrete_pts_list, + cpp_sgd_params_kg, num_to_sample, num_mc=2 ** 7) + + elif method == 'EI': + # EI method + next_points, voi = bayesian_optimization.gen_sample_from_qei(cpp_gp_loglikelihood.models[0], cpp_search_domain, + cpp_sgd_params_kg, num_to_sample, num_mc=2 ** 10) + else: + print(method + str(" not supported")) + sys.exit(0) + + print(method + " takes "+str((time.time()-time1))+" seconds") + #time1 = time.time() + print(method + " suggests points:") + print(next_points) + + sampled_points = [SamplePoint(pt, objective_func.evaluate(pt)[observations], objective_func._sample_var) for pt in next_points] + + #print "evaluating takes "+str((time.time()-time1)/60)+" mins" + capitals = np.ones(num_to_sample) + for i in range(num_to_sample): + if num_fidelity > 0: + value = 1.0 + for j in range(num_fidelity): + value *= next_points[i, dim-1-j] + capitals[i] = value + capital_so_far += np.amax(capitals) + print("evaluating takes capital " + str(capital_so_far) +" so far") + + # retrain the model + time1 = time.time() + + cpp_gp_loglikelihood.add_sampled_points(sampled_points) + cpp_gp_loglikelihood.train() + + print("retraining the model takes "+str((time.time()-time1))+" seconds") + time1 = time.time() + + # report the point + if method == 'KG': + eval_pts = inner_search_domain.generate_uniform_random_points_in_domain(int(1e4)) + eval_pts = np.reshape(np.append(eval_pts, (cpp_gp_loglikelihood.get_historical_data_copy()).points_sampled[:, :(cpp_gp_loglikelihood.dim-objective_func._num_fidelity)]), + (eval_pts.shape[0] + cpp_gp_loglikelihood._num_sampled, cpp_gp_loglikelihood.dim-objective_func._num_fidelity)) + + ps = PosteriorMeanMCMC(cpp_gp_loglikelihood.models, num_fidelity) + test = np.zeros(eval_pts.shape[0]) + for i, pt in enumerate(eval_pts): + ps.set_current_point(pt.reshape((1, cpp_gp_loglikelihood.dim-objective_func._num_fidelity))) + test[i] = -ps.compute_objective_function() + # initial_point = eval_pts[np.argmin(test)].reshape((1, cpp_gp_loglikelihood.dim-objective_func._num_fidelity)) + top_k = np.argsort(test)[:num_opt_start] + initial_point = np.reshape([eval_pts[t] for t in top_k], + (num_opt_start,cpp_gp_loglikelihood.dim-objective_func._num_fidelity)) + + py_repeated_search_domain = RepeatedDomain(num_repeats = 1, domain = inner_search_domain) + ps_mean_opt = pyGradientDescentOptimizer(py_repeated_search_domain, ps, py_sgd_params_ps) + report_point = multistart_optimize(ps_mean_opt, initial_point, num_multistarts=num_opt_start)[0] + + ps.set_current_point(report_point.reshape((1, cpp_gp_loglikelihood.dim-objective_func._num_fidelity))) + if -ps.compute_objective_function() > np.min(test): + report_point = initial_point[0].reshape((1, cpp_gp_loglikelihood.dim - objective_func._num_fidelity)) + else: + cpp_gp = cpp_gp_loglikelihood.models[0] + report_point = (cpp_gp.get_historical_data_copy()).points_sampled[np.argmin(cpp_gp._points_sampled_value[:, 0])] + + report_point = report_point.ravel() + report_point = np.concatenate((report_point, np.ones(objective_func._num_fidelity))) + + print() + print("Optimization finished successfully!") + print("The recommended point: ", end=' ') + print(report_point) + vall=objective_func.evaluate_true(report_point)[0] + print("recommending the point takes "+str((time.time()-time1))+" seconds") + print(method + ", VOI {0}, best so far {1}".format(voi, vall[0])) + + s_suggest=np.append(s_suggest, [sampled_points[0][0]], axis=0) + f_s_suggest=np.append(f_s_suggest, sampled_points[0][1], axis=0) + s_recommend=np.append(s_recommend, [report_point], axis=0) + f_s_recommend = np.append(f_s_recommend, [vall], axis=0) + elapsed[0, n + initial_n] = time.time() - start + pickle.dump([f_s_suggest, elapsed, s_suggest, s_recommend, f_s_recommend, high_to_low, sign], open(file_name, 'wb')) + # Collecting the logfile + logfile = open(file_name + '.out', 'w') + data_str = data_str + 'Iter: ' + str(n) + '------------------------------' + '\n' + data_str = data_str + 'Top_k values of mean posterior:' + str([test[t] for t in top_k]) + '\n' + data_str = data_str + 'True values of suggested points:' + str(objective_func.evaluate_true(s_suggest)) + '\n' + data_str = data_str + 'Noisy values of suggested points:' + str(f_s_suggest) + '\n' + data_str = data_str + 'True values of recommended points:' + str(f_s_recommend) + '\n' + logfile.writelines(data_str) + logfile.close() + + diff --git a/mylib/lib_REMBO/HesBO/README.md b/mylib/lib_REMBO/HesBO/README.md new file mode 100644 index 0000000..a382d09 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/README.md @@ -0,0 +1,53 @@ +# A Framework for Bayesian Optimization in Embedded Subspaces + +## What is high-dimensional Bayesian optimization? +Bayesian optimization (BO) has recently emerged as powerful method for the global optimization of expensive-to-evaluate black-box functions. However, these methods are usually limited to about 15 input parameters (levers). +In the paper "A Framework for Bayesian Optimization in Embedded Subspaces" (to appear at ICML'19), [Munteanu](https://www.statistik.tu-dortmund.de/munteanu.html "Alexander Munteanu"), Nayebi, and [Poloczek](http://www.sie.arizona.edu/poloczek "Matthias Poloczek") propose a non-adaptive probabilistic subspace embedding that can be combined with many BO algorithms to enable them to higher dimensional problems. + +This repository provides Python implementations of several algorithms that extend BO to problems with high input dimensions: + +* The HeSBO algorithm proposed by Munteanu, Nayebi, and Poloczek (ICML '19) (see below for the citation) combined with + + * The Knowledge Gradient (KG) algorithm of [Cornell-MOE](https://github.com/wujian16/Cornell-MOE "Cornell-MOE") (Wu & Frazier NIPS'16; Wu, Poloczek, Wilson, and Frazier NIPS'17) + + * The [BLOSSOM algorithm](https://github.com/markm541374/gpbo "BLOSSOM") of McLeod, Osborne, and Roberts (ICML '18) + + * Expected improvement, e.g., see Jones, Schonlau, and Welch (JGO '98) + +* The REMBO method using + + * the KXand Ky kernels of Wang et al. (JMLR '18) and + + * the K kernel of Binois, Ginsbourger and Roustant (LION '15). + +## Installing the requirements +The codes are written in python 3.6, so it is recommended to use this version of python to run the scripts. To install the requirements one can simply use this line: +```bash +pip3 install -r requirements.txt +``` +## Running different BO methods +There are HeSBO and three different variants of REMBO implemented in this code. Three REMBO variants are called Ky, KX, and K . These algorithms can be run as follows. + +```bash +python experiments.py [algorithm] [first_job_id] [last_job_id] [test_function] [num_of_steps] [low_dim] [high_dim] [num_of_initial_sample] [noise_variance] [REMBO_variant] +``` +To determine the algorithm, use `REMBO` or `HeSBO` input for the python script. If REMBO algorithm is selected to be run, the REMBO variant must be determined by `X`, `Y`, or `psi` as the last argument. If none of those variants is picked, all of those variants will be run. +Here is an example of running HeSBO-EI on 100 dim noise-free Branin with 4 low dimensions: +```bash +python experiments.py HeSBO 1 1 Branin 80 4 100 10 0 +``` +To collect the output data, you must have a folder named "results". Here is a plot for running REMBO-K and HeSBO-EI on the Branin function. +
+ +## Citation +```bash +@inproceedings{HeSBO19, + author = {Alex Munteanu and + Amin Nayebi and + Matthias Poloczek}, + title = {A Framework for Bayesian Optimization in Embedded Subspaces}, + booktitle = {Proceedings of the 36th International Conference on Machine Learning, {(ICML)}}, + year = {2019}, + note={Accepted for publication. The code is available at https://github.com/aminnayebi/HesBO.} +} +``` diff --git a/mylib/lib_REMBO/HesBO/REMBO.py b/mylib/lib_REMBO/HesBO/REMBO.py new file mode 100644 index 0000000..b885805 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/REMBO.py @@ -0,0 +1,495 @@ +import GPy +# import matlab.engine +import numpy as np +import math +from pyDOE import lhs +from scipy.stats import norm +import functions +import projection_matrix +import projections +import kernel_inputs +import timeit + +def EI(D_size,f_max,mu,var): + """ + :param D_size: number of points for which EI function will be calculated + :param f_max: the best value found for the test function so far + :param mu: a vector of predicted values for mean of the test function + corresponding to the points + :param var: a vector of predicted values for variance of the test function + corresponding to the points + :return: a vector of EI values of the points + """ + ei=np.zeros((D_size,1)) + std_dev=np.sqrt(var) + for i in range(D_size): + if var[i]!=0: + z= (mu[i] - f_max) / std_dev[i] + ei[i]= (mu[i]-f_max) * norm.cdf(z) + std_dev[i] * norm.pdf(z) + return ei + +def RunRembo(low_dim=2, high_dim=20, initial_n=20, total_itr=100, func_type='Branin', + matrix_type='simple', kern_inp_type='Y', A_input=None, s=None, active_var=None, + hyper_opt_interval=20, ARD=False, variance=1., length_scale=None, box_size=None, + noise_var=0): + """" + + :param low_dim: the dimension of low dimensional search space + :param high_dim: the dimension of high dimensional search space + :param initial_n: the number of initial points + :param total_itr: the number of iterations of algorithm. The total + number of test function evaluations is initial_n + total_itr + :param func_type: the name of test function + :param matrix_type: the type of projection matrix + :param kern_inp_type: the type of projection. Projected points + are used as the input of kernel + :param A_input: a projection matrix with iid gaussian elements. + The size of matrix is low_dim * high_dim + :param s: initial points + :param active_var: a vector with the size of greater or equal to + the number of active variables of test function. The values of + vector are integers less than high_dim value. + :param hyper_opt_interval: the number of iterations between two consecutive + hyper parameters optimizations + :param ARD: if TRUE, kernel is isomorphic + :param variance: signal variance of the kernel + :param length_scale: length scale values of the kernel + :param box_size: this variable indicates the search space [-box_size, box_size]^d + :param noise_var: noise variance of the test functions + :return: a tuple of best values of each iteration, all observed points, and + corresponding test function values of observed points + """ + + if active_var is None: + active_var= np.arange(high_dim) + if box_size is None: + box_size=math.sqrt(low_dim) + if hyper_opt_interval is None: + hyper_opt_interval = 10 + + #Specifying the type of objective function + if func_type=='Branin': + test_func = functions.Branin(active_var, noise_var=noise_var) + elif func_type=='Rosenbrock': + test_func = functions.Rosenbrock(active_var, noise_var=noise_var) + elif func_type=='Hartmann6': + test_func = functions.Hartmann6(active_var, noise_var=noise_var) + elif func_type == 'StybTang': + test_func = functions.StybTang(active_var, noise_var=noise_var) + else: + TypeError('The input for func_type variable is invalid, which is', func_type) + return + + #Specifying the type of embedding matrix + if matrix_type=='simple': + matrix=projection_matrix.SimpleGaussian(low_dim, high_dim) + elif matrix_type=='normal': + matrix= projection_matrix.Normalized(low_dim, high_dim) + elif matrix_type=='orthogonal': + matrix = projection_matrix.Orthogonalized(low_dim, high_dim) + else: + TypeError('The input for matrix_type variable is invalid, which is', matrix_type) + return + + # Generating matrix A + if A_input is not None: + matrix.A = A_input + + A = matrix.evaluate() + + #Specifying the input type of kernel + if kern_inp_type=='Y': + kern_inp = kernel_inputs.InputY(A) + input_dim=low_dim + elif kern_inp_type=='X': + kern_inp = kernel_inputs.InputX(A) + input_dim = high_dim + elif kern_inp_type == 'psi': + kern_inp = kernel_inputs.InputPsi(A) + input_dim = high_dim + else: + TypeError('The input for kern_inp_type variable is invalid, which is', kern_inp_type) + return + + #Specifying the convex projection + cnv_prj=projections.ConvexProjection(A) + + best_results=np.zeros([1,total_itr + initial_n]) + elapsed = np.zeros([1, total_itr + initial_n]) + + # Initiating first sample # Sample points are in [-d^1/2, d^1/2] + if s is None: + s = lhs(low_dim, initial_n) * 2 * box_size - box_size + f_s = test_func.evaluate(cnv_prj.evaluate(s)) + f_s_true = test_func.evaluate_true(cnv_prj.evaluate(s)) + for i in range(initial_n): + best_results[0,i]=np.max(f_s_true[0:i+1]) + + # Generating GP model + k = GPy.kern.Matern52(input_dim=input_dim, ARD=ARD, variance=variance, lengthscale=length_scale) + m = GPy.models.GPRegression(kern_inp.evaluate(s), f_s, kernel=k) + m.likelihood.variance = 1e-6 + + # Main loop of the algorithm + for i in range(total_itr): + + start = timeit.default_timer() + # Updating GP model + m.set_XY(kern_inp.evaluate(s),f_s) + if (i+initial_n<=25 and i % 5 == 0) or (i+initial_n>25 and i % hyper_opt_interval == 0): + m.optimize() + + # finding the next point for sampling + D = lhs(low_dim, 2000) * 2 * box_size - box_size + mu, var = m.predict(kern_inp.evaluate(D)) + ei_d = EI(len(D), max(f_s), mu, var) + index = np.argmax(ei_d) + s = np.append(s, [D[index]], axis=0) + f_s = np.append(f_s, test_func.evaluate(cnv_prj.evaluate([D[index]])), axis=0) + f_s_true = np.append(f_s_true, test_func.evaluate_true(cnv_prj.evaluate([D[index]])), axis=0) + + #Collecting data + stop = timeit.default_timer() + best_results[0,i + initial_n]=np.max(f_s_true) + elapsed[0, i + initial_n] = stop - start + + # if func_type == 'WalkerSpeed': + # eng.quit() + + return best_results, elapsed, s, f_s, f_s_true, cnv_prj.evaluate(s) + + +class RemboSetter: + r""" + This corresponds to just a kind of object to perform REMBO. + """ + + def __init__(self, + random_seed:int=42, + sample_zero:bool = False): + r""" + Class Constructor + """ + self.random_seed:int = random_seed + self.sample_zero:bool = sample_zero + # Start the optimization properties + self.__acq_opt_time = np.NAN + self.__mode_fit_time = np.NAN + self.__cum_iteration_time = np.NAN + + + def optimize(self, func, low_dim=2, high_dim=20, initial_n=20, total_itr=100, + matrix_type='simple', kern_inp_type='Y', A_input=None, s=None, active_var=None, + hyper_opt_interval=20, ARD=False, variance=1., length_scale=None, box_size=None, + noise_var=0)->tuple: + + """" + + :param func: Some defined function with a call method + :param low_dim: the dimension of low dimensional search space + :param high_dim: the dimension of high dimensional search space + :param initial_n: the number of initial points + :param total_itr: the number of iterations of algorithm. The total + number of test function evaluations is initial_n + total_itr + :param matrix_type: the type of projection matrix + :param kern_inp_type: the type of projection. Projected points + are used as the input of kernel + :param A_input: a projection matrix with iid gaussian elements. + The size of matrix is low_dim * high_dim + :param s: initial points + :param active_var: a vector with the size of greater or equal to + the number of active variables of test function. The values of + vector are integers less than high_dim value. + :param hyper_opt_interval: the number of iterations between two consecutive + hyper parameters optimizations + :param ARD: if TRUE, kernel is isomorphic + :param variance: signal variance of the kernel + :param length_scale: length scale values of the kernel + :param box_size: this variable indicates the search space [-box_size, box_size]^d + :param noise_var: noise variance of the test functions + :return: a tuple of best values of each iteration, all observed points, and + corresponding test function values of observed points + """ + + if active_var is None: + active_var= np.arange(high_dim) + if box_size is None: + box_size=math.sqrt(low_dim) + if hyper_opt_interval is None: + hyper_opt_interval = 10 + + ### Include this to assign + ### Modification by: @Ivan Olarte Rodriguez 29-11-2024 - 13:45 + func.var = noise_var + func.act_var = active_var + + test_func = func + #Specifying the type of embedding matrix + if matrix_type=='simple': + matrix=projection_matrix.SimpleGaussian(low_dim, high_dim) + elif matrix_type=='normal': + matrix= projection_matrix.Normalized(low_dim, high_dim) + elif matrix_type=='orthogonal': + matrix = projection_matrix.Orthogonalized(low_dim, high_dim) + else: + TypeError('The input for matrix_type variable is invalid, which is', matrix_type) + return + + # Generating matrix A + if A_input is not None: + matrix.A = A_input + + A = matrix.evaluate() + + #Specifying the input type of kernel + if kern_inp_type=='Y': + kern_inp = kernel_inputs.InputY(A) + input_dim=low_dim + elif kern_inp_type=='X': + kern_inp = kernel_inputs.InputX(A) + input_dim = high_dim + elif kern_inp_type == 'psi': + kern_inp = kernel_inputs.InputPsi(A) + input_dim = high_dim + else: + TypeError('The input for kern_inp_type variable is invalid, which is', kern_inp_type) + return + + #Specifying the convex projection + cnv_prj=projections.ConvexProjection(A) + + best_results=np.zeros([1,total_itr + initial_n]) + elapsed = np.zeros([1, total_itr + initial_n]) + + # Initiating first sample # Sample points are in [-d^1/2, d^1/2] + if s is None: + s = lhs(low_dim, initial_n) * 2 * box_size - box_size + + #### + # NOTE: The following is a placeholder for sampling a 0 vector + if self.sample_zero: + s[0,:] = np.zeros_like(s[0,:],dtype=float) + #### + f_s = test_func.evaluate(cnv_prj.evaluate(s)) + #f_s_true = test_func.evaluate_true(cnv_prj.evaluate(s)) + for i in range(initial_n): + best_results[0,i]=np.max(f_s) + + # Generating GP model + k = GPy.kern.Matern52(input_dim=input_dim, ARD=ARD, variance=variance, lengthscale=length_scale) + m = GPy.models.GPRegression(kern_inp.evaluate(s), f_s, kernel=k) + m.likelihood.variance = 1e-7 + + # Main loop of the algorithm + for i in range(total_itr): + + start = timeit.default_timer() + # Updating GP model + + start_model_fitting = timeit.default_timer() + m.set_XY(kern_inp.evaluate(s),f_s) + if (i+initial_n<=25 and i % 5 == 0) or (i+initial_n>25 and i % hyper_opt_interval == 0): + m.optimize(max_iters=5000) + + self.__mode_fit_time = timeit.default_timer() - start_model_fitting + + # finding the next point for sampling + + start_acquisition_function_time = timeit.default_timer() + D = lhs(low_dim, 2000) * 2 * box_size - box_size + mu, var = m.predict(kern_inp.evaluate(D)) + ei_d = EI(len(D), max(f_s), mu, var) + index = np.argmax(ei_d) + self.__acq_opt_time= timeit.default_timer() - start_acquisition_function_time + + + s = np.append(s, [D[index]], axis=0) + f_s = np.append(f_s, test_func.evaluate(cnv_prj.evaluate([D[index]])), axis=0) + #f_s_true = np.append(f_s_true, test_func.evaluate_true(cnv_prj.evaluate([D[index]])), axis=0) + + + #Collecting data + stop = timeit.default_timer() + best_results[0,i + initial_n]=np.max(f_s) + elapsed[0, i + initial_n] = stop - start + + self.__cum_iteration_time = np.sum(elapsed) + + # if func_type == 'WalkerSpeed': + # eng.quit() + + return best_results, elapsed, s, f_s, cnv_prj.evaluate(s) + + + @property + def acq_opt_time(self)->float: + r""" + Return the computation of the acquisition function + """ + return self.__acq_opt_time + + @property + def mode_fit_time(self)->float: + r""" + Return the GPR Fitting time + """ + return self.__mode_fit_time + + @property + def cum_iteration_time(self)->float: + r""" + Return the cumulative time if algorithm runs + """ + return self.__cum_iteration_time + + + + +def RunRembo2(func, low_dim=2, high_dim=20, initial_n=20, total_itr=100, + matrix_type='simple', kern_inp_type='X', A_input=None, s=None, active_var=None, + hyper_opt_interval=20, ARD=False, variance=1., length_scale=None, box_size=None, + noise_var=0): + """" + + :param func: Some defined function with a call method + :param low_dim: the dimension of low dimensional search space + :param high_dim: the dimension of high dimensional search space + :param initial_n: the number of initial points + :param total_itr: the number of iterations of algorithm. The total + number of test function evaluations is initial_n + total_itr + :param matrix_type: the type of projection matrix + :param kern_inp_type: the type of projection. Projected points + are used as the input of kernel + :param A_input: a projection matrix with iid gaussian elements. + The size of matrix is low_dim * high_dim + :param s: initial points + :param active_var: a vector with the size of greater or equal to + the number of active variables of test function. The values of + vector are integers less than high_dim value. + :param hyper_opt_interval: the number of iterations between two consecutive + hyper parameters optimizations + :param ARD: if TRUE, kernel is isomorphic + :param variance: signal variance of the kernel + :param length_scale: length scale values of the kernel + :param box_size: this variable indicates the search space [-box_size, box_size]^d + :param noise_var: noise variance of the test functions + :return: a tuple of best values of each iteration, all observed points, and + corresponding test function values of observed points + """ + + if active_var is None: + active_var= np.arange(high_dim) + if box_size is None: + box_size=math.sqrt(low_dim) + if hyper_opt_interval is None: + hyper_opt_interval = 10 + + # #Specifying the type of objective function + # if func_type=='Branin': + # test_func = functions.Branin(active_var, noise_var=noise_var) + # elif func_type=='Rosenbrock': + # test_func = functions.Rosenbrock(active_var, noise_var=noise_var) + # elif func_type=='Hartmann6': + # test_func = functions.Hartmann6(active_var, noise_var=noise_var) + # elif func_type == 'StybTang': + # test_func = functions.StybTang(active_var, noise_var=noise_var) + # else: + # TypeError('The input for func_type variable is invalid, which is', func_type) + # return + + ### Include this + ### Modification by: @Ivan Olarte Rodriguez 29-11-2024 - 13:45 + func.var = noise_var + func.act_var = active_var + + test_func = func + #Specifying the type of embedding matrix + if matrix_type=='simple': + matrix=projection_matrix.SimpleGaussian(low_dim, high_dim) + elif matrix_type=='normal': + matrix= projection_matrix.Normalized(low_dim, high_dim) + elif matrix_type=='orthogonal': + matrix = projection_matrix.Orthogonalized(low_dim, high_dim) + else: + TypeError('The input for matrix_type variable is invalid, which is', matrix_type) + return + + # Generating matrix A + if A_input is not None: + matrix.A = A_input + + A = matrix.evaluate() + + #Specifying the input type of kernel + if kern_inp_type=='Y': + kern_inp = kernel_inputs.InputY(A) + input_dim=low_dim + elif kern_inp_type=='X': + kern_inp = kernel_inputs.InputX(A) + input_dim = high_dim + elif kern_inp_type == 'psi': + kern_inp = kernel_inputs.InputPsi(A) + input_dim = high_dim + else: + TypeError('The input for kern_inp_type variable is invalid, which is', kern_inp_type) + return + + #Specifying the convex projection + cnv_prj=projections.ConvexProjection(A) + + best_results=np.zeros([1,total_itr + initial_n]) + elapsed = np.zeros([1, total_itr + initial_n]) + + # Initiating first sample # Sample points are in [-d^1/2, d^1/2] + if s is None: + s = lhs(low_dim, initial_n) * 2 * box_size - box_size + f_s = test_func.evaluate(cnv_prj.evaluate(s)) + #f_s_true = test_func.evaluate_true(cnv_prj.evaluate(s)) + for i in range(initial_n): + best_results[0,i]=np.max(f_s) + + # Generating GP model + k = GPy.kern.Matern52(input_dim=input_dim, ARD=ARD, variance=variance, lengthscale=length_scale) + m = GPy.models.GPRegression(kern_inp.evaluate(s), f_s, kernel=k) + m.likelihood.variance = 1e-6 + + # Main loop of the algorithm + for i in range(total_itr): + + start = timeit.default_timer() + # Updating GP model + m.set_XY(kern_inp.evaluate(s),f_s) + if (i+initial_n<=25 and i % 5 == 0) or (i+initial_n>25 and i % hyper_opt_interval == 0): + m.optimize() + + # finding the next point for sampling + D = lhs(low_dim, 2000) * 2 * box_size - box_size + mu, var = m.predict(kern_inp.evaluate(D)) + ei_d = EI(len(D), max(f_s), mu, var) + index = np.argmax(ei_d) + s = np.append(s, [D[index]], axis=0) + f_s = np.append(f_s, test_func.evaluate(cnv_prj.evaluate([D[index]])), axis=0) + #f_s_true = np.append(f_s_true, test_func.evaluate_true(cnv_prj.evaluate([D[index]])), axis=0) + + + #Collecting data + stop = timeit.default_timer() + best_results[0,i + initial_n]=np.max(f_s) + elapsed[0, i + initial_n] = stop - start + + # if func_type == 'WalkerSpeed': + # eng.quit() + + return best_results, elapsed, s, f_s, cnv_prj.evaluate(s) + +if __name__=='__main__': + best, elapsed, s, f_s, fs_true, high_s =RunRembo(low_dim=1, + high_dim=2, + func_type='Branin', + initial_n=10, + total_itr=50, + kern_inp_type='X', + ARD=True, + noise_var=0) + + diff --git a/mylib/lib_REMBO/HesBO/__pycache__/REMBO.cpython-310.pyc b/mylib/lib_REMBO/HesBO/__pycache__/REMBO.cpython-310.pyc new file mode 100644 index 0000000..0e10cc6 Binary files /dev/null and b/mylib/lib_REMBO/HesBO/__pycache__/REMBO.cpython-310.pyc differ diff --git a/mylib/lib_REMBO/HesBO/__pycache__/functions.cpython-310.pyc b/mylib/lib_REMBO/HesBO/__pycache__/functions.cpython-310.pyc new file mode 100644 index 0000000..a55ccd3 Binary files /dev/null and b/mylib/lib_REMBO/HesBO/__pycache__/functions.cpython-310.pyc differ diff --git a/mylib/lib_REMBO/HesBO/__pycache__/kernel_inputs.cpython-310.pyc b/mylib/lib_REMBO/HesBO/__pycache__/kernel_inputs.cpython-310.pyc new file mode 100644 index 0000000..02fdedc Binary files /dev/null and b/mylib/lib_REMBO/HesBO/__pycache__/kernel_inputs.cpython-310.pyc differ diff --git a/mylib/lib_REMBO/HesBO/__pycache__/projection_matrix.cpython-310.pyc b/mylib/lib_REMBO/HesBO/__pycache__/projection_matrix.cpython-310.pyc new file mode 100644 index 0000000..013da93 Binary files /dev/null and b/mylib/lib_REMBO/HesBO/__pycache__/projection_matrix.cpython-310.pyc differ diff --git a/mylib/lib_REMBO/HesBO/__pycache__/projections.cpython-310.pyc b/mylib/lib_REMBO/HesBO/__pycache__/projections.cpython-310.pyc new file mode 100644 index 0000000..0c0d4a9 Binary files /dev/null and b/mylib/lib_REMBO/HesBO/__pycache__/projections.cpython-310.pyc differ diff --git a/mylib/lib_REMBO/HesBO/count_sketch.py b/mylib/lib_REMBO/HesBO/count_sketch.py new file mode 100644 index 0000000..12e8992 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/count_sketch.py @@ -0,0 +1,142 @@ +import GPy +# import matlab.engine +import numpy as np +from pyDOE import lhs +import functions +from REMBO import EI +import timeit + +def dim_sampling(low_dim, X, bx_size): + if len(X.shape)==1: + X=X.reshape((1, X.shape[0])) + n=X.shape[0] + high_dim=X.shape[1] + low_obs=np.zeros((n,low_dim)) + high_to_low=np.zeros(high_dim,dtype=int) + sign=np.random.choice([-1,1],high_dim) + for i in range(high_dim): + high_to_low[i]=np.random.choice(range(low_dim)) + low_obs[:,high_to_low[i]]=X[:,i]*sign[i]+ low_obs[:,high_to_low[i]] + + for i in range(n): + for j in range(low_dim): + if low_obs[i][j] > bx_size: low_obs[i][j] = bx_size + elif low_obs[i][j] < -bx_size: low_obs[i][j] = -bx_size + return low_obs, high_to_low, sign + +def back_projection(low_obs, high_to_low, sign, bx_size): + if len(low_obs.shape)==1: + low_obs=low_obs.reshape((1, low_obs.shape[0])) + n=low_obs.shape[0] + high_dim=high_to_low.shape[0] + low_dim=low_obs.shape[1] + high_obs=np.zeros((n,high_dim)) + scale=1 + for i in range(high_dim): + high_obs[:,i]=sign[i]*low_obs[:,high_to_low[i]]*scale + for i in range(n): + for j in range(high_dim): + if high_obs[i][j] > bx_size: high_obs[i][j] = bx_size + elif high_obs[i][j] < -bx_size: high_obs[i][j] = -bx_size + return high_obs + +def RunMain(low_dim=2, high_dim=25, initial_n=20, total_itr=100, func_type='Branin', + s=None, active_var=None, ARD=False, variance=1., length_scale=None, box_size=None, + high_to_low=None, sign=None, hyper_opt_interval=20, noise_var=0): + """ + + :param high_dim: the dimension of high dimensional search space + :param low_dim: The effective dimension of the algorithm. + :param initial_n: the number of initial points + :param total_itr: the number of iterations of algorithm. The total + number of test function evaluations is initial_n + total_itr + :param func_type: the name of test function + :param s: initial points + :param active_var: a vector with the size of greater or equal to + the number of active variables of test function. The values of + vector are integers less than high_dim value. + :param ARD: if TRUE, kernel is isomorphic + :param variance: signal variance of the kernel + :param length_scale: length scale values of the kernel + :param box_size: this variable indicates the search space [-box_size, box_size]^d + :param high_to_low: a vector with D elements. each element can have a value from {0,..,d-1} + :param sign: a vector with D elements. each element is either +1 or -1. + :param hyper_opt_interval: the number of iterations between two consecutive + hyper parameters optimizations + :param noise_var: noise variance of the test functions + :return: a tuple of best values of each iteration, all observed points, and + corresponding test function values of observed points + """ + + if active_var is None: + active_var= np.arange(high_dim) + if box_size is None: + box_size=1 + if high_to_low is None: + high_to_low=np.random.choice(range(low_dim), high_dim) + if sign is None: + sign = np.random.choice([-1, 1], high_dim) + + #Specifying the type of objective function + if func_type=='Branin': + test_func = functions.Branin(active_var, noise_var=noise_var) + elif func_type=='Rosenbrock': + test_func = functions.Rosenbrock(active_var, noise_var=noise_var) + elif func_type=='Hartmann6': + test_func = functions.Hartmann6(active_var, noise_var=noise_var) + elif func_type == 'StybTang': + test_func = functions.StybTang(active_var, noise_var=noise_var) + else: + TypeError('The input for func_type variable is invalid, which is', func_type) + return + + best_results = np.zeros([1, total_itr + initial_n]) + elapsed=np.zeros([1, total_itr + initial_n]) + + # Creating the initial points. The shape of s is nxD + if s is None: + s=lhs(low_dim, initial_n) * 2 * box_size - box_size + f_s = test_func.evaluate(back_projection(s,high_to_low,sign,box_size)) + f_s_true = test_func.evaluate_true(back_projection(s,high_to_low,sign,box_size)) + for i in range(initial_n): + best_results[0,i]=np.max(f_s_true[0:i+1]) + + # Building and fitting a new GP model + kern = GPy.kern.Matern52(input_dim=low_dim, ARD=ARD, variance=variance, lengthscale=length_scale) + m = GPy.models.GPRegression(s, f_s, kernel=kern) + m.likelihood.variance = 1e-3 + + # Main loop + for i in range(total_itr): + + start = timeit.default_timer() + + # Updating GP model + m.set_XY(s, f_s) + if (i+initial_n<=25 and i % 5 == 0) or (i+initial_n>25 and i % hyper_opt_interval == 0): + m.optimize() + + # Maximizing acquisition function + D = lhs(low_dim, 2000) * 2 * box_size - box_size + mu, var = m.predict(D) + ei_d = EI(len(D), max(f_s), mu, var) + index = np.argmax(ei_d) + + # Adding the new point to our sample + s = np.append(s, [D[index]], axis=0) + new_high_point=back_projection(D[index],high_to_low,sign,box_size) + f_s = np.append(f_s, test_func.evaluate(new_high_point), axis=0) + f_s_true = np.append(f_s_true, test_func.evaluate_true(new_high_point), axis=0) + + stop = timeit.default_timer() + best_results[0, i + initial_n] = np.max(f_s_true) + elapsed[0, i + initial_n]=stop-start + + # if func_type == 'WalkerSpeed': + # eng.quit() + high_s = back_projection(s,high_to_low,sign,box_size) + return best_results, elapsed, s, f_s, f_s_true, high_s + +if __name__=='__main__': + res, time, s, f_s, f_s_true, _=RunMain(low_dim=8, high_dim=25, initial_n=20, total_itr=50, ARD=True, noise_var=1) + print(res,time) diff --git a/mylib/lib_REMBO/HesBO/experiments.py b/mylib/lib_REMBO/HesBO/experiments.py new file mode 100644 index 0000000..18f295e --- /dev/null +++ b/mylib/lib_REMBO/HesBO/experiments.py @@ -0,0 +1,238 @@ +import REMBO +import count_sketch +import numpy as np +import pickle +import timeit +import sys +from random import sample +from pyDOE import lhs + +def REMBO_experiments(start_rep=1, stop_rep=50, test_func='Rosenbrock', total_itr=100, + low_dim=2, high_dim=25, initial_n=20, opt_interval=20, ARD=False, + box_size=None, noise_var=0): + if box_size is None: + box_size=np.sqrt(low_dim) + + all_A = np.random.normal(0, 1, [stop_rep, low_dim, high_dim]) + all_s = np.empty((stop_rep, initial_n, low_dim)) + + for i in range(stop_rep): + all_s[i] = lhs(low_dim, initial_n) * 2 * box_size - box_size + + result_x_obj = np.empty((0, total_itr+initial_n)) + result_y_obj = np.empty((0, total_itr+initial_n)) + result_psi_obj = np.empty((0, total_itr+initial_n)) + + elapsed_x = np.empty((0, total_itr + initial_n)) + elapsed_y = np.empty((0, total_itr + initial_n)) + elapsed_psi = np.empty((0, total_itr + initial_n)) + + result_x_s = np.empty((0, initial_n + total_itr, low_dim)) + result_x_f_s = np.empty((0, initial_n + total_itr, 1)) + result_y_s = np.empty((0, initial_n + total_itr, low_dim)) + result_y_f_s = np.empty((0, initial_n + total_itr, 1)) + result_psi_s = np.empty((0, initial_n + total_itr, low_dim)) + result_psi_f_s = np.empty((0, initial_n + total_itr, 1)) + + for i in range(start_rep - 1, stop_rep): + start = timeit.default_timer() + active_var = sample(range(high_dim), low_dim) + + # Running different algorithms to solve Hartmann6 function + temp_result, temp_elapsed, temp_s, temp_f_s, _, _ = REMBO.RunRembo(low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, + total_itr=total_itr, func_type=test_func, A_input=all_A[i], + s=all_s[i], kern_inp_type='Y', matrix_type='simple', + hyper_opt_interval=opt_interval, ARD=ARD, box_size=box_size, + noise_var=noise_var) + result_y_obj = np.append(result_y_obj, temp_result, axis=0) + elapsed_y = np.append(elapsed_y, temp_elapsed, axis=0) + result_y_s = np.append(result_y_s, [temp_s], axis=0) + result_y_f_s = np.append(result_y_f_s, [temp_f_s], axis=0) + + temp_result, temp_elapsed, temp_s, temp_f_s, _, _ = REMBO.RunRembo(low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, + total_itr=total_itr, func_type=test_func, A_input=all_A[i], + s=all_s[i], kern_inp_type='X', matrix_type='simple', + hyper_opt_interval=opt_interval, ARD=ARD, box_size=box_size, + noise_var=noise_var) + result_x_obj = np.append(result_x_obj, temp_result, axis=0) + elapsed_x = np.append(elapsed_x, temp_elapsed, axis=0) + result_x_s = np.append(result_x_s, [temp_s], axis=0) + result_x_f_s = np.append(result_x_f_s, [temp_f_s], axis=0) + + temp_result, temp_elapsed, temp_s, temp_f_s, _, _ = REMBO.RunRembo(low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, + total_itr=total_itr, func_type=test_func, A_input=all_A[i], + s=all_s[i], kern_inp_type='psi', matrix_type='simple', + hyper_opt_interval=opt_interval, ARD=ARD, box_size=box_size, + noise_var=noise_var) + result_psi_obj = np.append(result_psi_obj, temp_result, axis=0) + elapsed_psi = np.append(elapsed_psi, temp_elapsed, axis=0) + result_psi_s = np.append(result_psi_s, [temp_s], axis=0) + result_psi_f_s = np.append(result_psi_f_s, [temp_f_s], axis=0) + + stop = timeit.default_timer() + + print(i) + print(stop - start) + + # Saving the results for Hartmann6 in a pickle + if test_func=='Rosenbrock': + file_name = 'result/rosenbrock_results_d'+str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func=='Branin': + file_name = 'result/branin_results_d'+str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'Hartmann6': + file_name = 'result/hartmann6_results_d'+str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'StybTang': + file_name = 'result/stybtang_results_d'+str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'WalkerSpeed': + file_name = 'result/walkerspeed_results_d'+str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'MNIST': + file_name = 'result/mnist_results_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + + fileObject = open(file_name, 'wb') + pickle.dump(result_y_obj, fileObject) + pickle.dump(result_x_obj, fileObject) + pickle.dump(result_psi_obj, fileObject) + + pickle.dump(elapsed_y, fileObject) + pickle.dump(elapsed_x, fileObject) + pickle.dump(elapsed_psi, fileObject) + + pickle.dump(result_y_s, fileObject) + pickle.dump(result_x_s, fileObject) + pickle.dump(result_psi_s, fileObject) + + pickle.dump(result_y_f_s, fileObject) + pickle.dump(result_x_f_s, fileObject) + pickle.dump(result_psi_f_s, fileObject) + fileObject.close() + +def REMBO_separate(start_rep=1, stop_rep=50, test_func='Rosenbrock', total_itr=100, low_dim=2, + high_dim=25, initial_n=20, opt_interval=20, ARD=False, box_size=None, + kern_inp_type='Y', noise_var=0): + if box_size is None: + box_size=np.sqrt(low_dim) + + all_A = np.random.normal(0, 1, [stop_rep, low_dim, high_dim]) + all_s = np.empty((stop_rep, initial_n, low_dim)) + + for i in range(stop_rep): + all_s[i] = lhs(low_dim, initial_n) * 2 * box_size - box_size + + result_x_obj = np.empty((0, total_itr+initial_n)) + elapsed_x = np.empty((0, total_itr + initial_n)) + result_x_s = np.empty((0, initial_n + total_itr, low_dim)) + result_x_f_s = np.empty((0, initial_n + total_itr, 1)) + result_high_s = np.empty((0, initial_n + total_itr, high_dim)) + for i in range(start_rep - 1, stop_rep): + start = timeit.default_timer() + active_var = sample(range(high_dim), low_dim) + + # Running different algorithms to solve Hartmann6 function + temp_result, temp_elapsed, temp_s, temp_f_s, _, temp_high_s = REMBO.RunRembo(low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, + total_itr=total_itr, func_type=test_func, A_input=all_A[i], + s=all_s[i], kern_inp_type=kern_inp_type, matrix_type='simple', + hyper_opt_interval=opt_interval, ARD=ARD, box_size=box_size, + noise_var=noise_var) + result_x_obj = np.append(result_x_obj, temp_result, axis=0) + elapsed_x = np.append(elapsed_x, temp_elapsed, axis=0) + result_high_s = np.append(result_high_s, [temp_high_s], axis=0) + result_x_s = np.append(result_x_s, [temp_s], axis=0) + result_x_f_s = np.append(result_x_f_s, [temp_f_s], axis=0) + + + stop = timeit.default_timer() + + print(i) + print(stop - start) + + # Saving the results for Hartmann6 in a pickle + if test_func=='Rosenbrock': + file_name = 'result/rosenbrock_results_' + kern_inp_type + '_d'+str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func=='Branin': + file_name = 'result/branin_results_' + kern_inp_type + '_d'+str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'Hartmann6': + file_name = 'result/hartmann6_results_' + kern_inp_type + '_d' + str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'StybTang': + file_name = 'result/stybtang_results_' + kern_inp_type + '_d' + str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'WalkerSpeed': + file_name = 'result/walkerspeed_results_' + kern_inp_type + '_d' + str(low_dim)+'_D'+str(high_dim)+'_n'+str(initial_n)+'_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'MNIST': + file_name = 'result/mnist_results_' + kern_inp_type + '_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + + fileObject = open(file_name, 'wb') + pickle.dump(result_x_obj, fileObject) + pickle.dump(elapsed_x, fileObject) + pickle.dump(result_high_s, fileObject) + pickle.dump(result_x_s, fileObject) + pickle.dump(result_x_f_s, fileObject) + + fileObject.close() + +def count_sketch_BO_experiments(start_rep=1, stop_rep=50, test_func='Rosenbrock', total_itr=100, + low_dim=2, high_dim=25, initial_n=20, ARD=False, box_size=None, + noise_var=0): + + result_obj = np.empty((0, total_itr+initial_n)) + elapsed = np.empty((0, total_itr + initial_n)) + result_s = np.empty((0, initial_n + total_itr, low_dim)) + result_f_s = np.empty((0, initial_n + total_itr, 1)) + result_high_s = np.empty((0, initial_n + total_itr, high_dim)) + + for i in range(start_rep - 1, stop_rep): + start = timeit.default_timer() + + temp_result, temp_elapsed, temp_s, temp_f_s, _, temp_high_s = count_sketch.RunMain(low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, + total_itr=total_itr, func_type=test_func, s=None, ARD=ARD, + box_size=box_size, noise_var=noise_var) + + result_obj = np.append(result_obj, temp_result, axis=0) + elapsed = np.append(elapsed, temp_elapsed, axis=0) + result_s = np.append(result_s, [temp_s], axis=0) + result_f_s = np.append(result_f_s, [temp_f_s], axis=0) + result_high_s = np.append(result_high_s, [temp_high_s], axis=0) + + stop = timeit.default_timer() + + print(i) + print(stop - start) + + # Saving the results for Hartmann6 in a pickle + if test_func == 'Rosenbrock': + file_name = 'result/rosenbrock_results_CS_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'Branin': + file_name = 'result/branin_results_CS_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'Hartmann6': + file_name = 'result/hartmann6_results_CS_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'StybTang': + file_name = 'result/stybtang_results_CS_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'WalkerSpeed': + file_name = 'result/walkerspeed_results_CS_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + elif test_func == 'MNIST': + file_name = 'result/mnist_results_CS_d' + str(low_dim) + '_D' + str(high_dim) + '_n' + str(initial_n) + '_rep_' + str(start_rep) + '_' + str(stop_rep) + + fileObject = open(file_name, 'wb') + pickle.dump(result_obj, fileObject) + pickle.dump(elapsed, fileObject) + pickle.dump(result_s, fileObject) + pickle.dump(result_f_s, fileObject) + fileObject.close() + + +if __name__=='__main__': + start_rep = int(sys.argv[2]) + stop_rep = int(sys.argv[3]) + test_func = sys.argv[4] + total_iter = int(sys.argv[5]) + low_dim = int(sys.argv[6]) + high_dim = int(sys.argv[7]) + initial_n = int(sys.argv[8]) + variance = int(sys.argv[9]) + + if sys.argv[1]=='REMBO': + if len(sys.argv)<=10: + REMBO_experiments(start_rep=start_rep, stop_rep=stop_rep, test_func=test_func, total_itr=total_iter, low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, ARD=True, noise_var=variance) + else: + kern_type = sys.argv[10] + REMBO_separate(start_rep=start_rep, stop_rep=stop_rep, test_func=test_func, total_itr=total_iter, low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, ARD=True, kern_inp_type=kern_type, noise_var=variance) + elif sys.argv[1]=='HeSBO': + count_sketch_BO_experiments(start_rep=start_rep, stop_rep=stop_rep, test_func=test_func, total_itr=total_iter, low_dim=low_dim, high_dim=high_dim, initial_n=initial_n, ARD=True, box_size=1, noise_var=variance) \ No newline at end of file diff --git a/mylib/lib_REMBO/HesBO/functions.py b/mylib/lib_REMBO/HesBO/functions.py new file mode 100644 index 0000000..24ef32f --- /dev/null +++ b/mylib/lib_REMBO/HesBO/functions.py @@ -0,0 +1,188 @@ +import math +import numpy as np +# import matlab.engine +# import torch +# from BOCK_benchmarks.mnist_weight import mnist_weight + +# All functions are defined in such a way that have global maximums, +# if a function originally has a minimum, the final objective value is multiplied by -1 + +class TestFunction: + def evaluate(self,x): + pass + +class Rosenbrock(TestFunction): + def __init__(self, act_var, noise_var=0): + self.range=np.array([[-2,2], + [-2,2]]) + self.act_var=act_var + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i,1] - self.range[i,0]) / 2 + ( + self.range[i,1] + self.range[i,0]) / 2 + return x_copy + + def evaluate_true(self,x): + # Calculating the output + scaled_x=self.scale_domain(x) + f = [[0]] + f[0] = [-(math.pow(1 - i[self.act_var[0]], 2) + 100 * math.pow(i[self.act_var[1]] - math.pow(i[self.act_var[0]], 2), 2)) for i in scaled_x] + f = np.transpose(f) + return f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class Branin(TestFunction): + def __init__(self, act_var, noise_var=0): + self.range=np.array([[-5,10], + [0,15]]) + self.act_var = act_var + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self,x): + scaled_x=self.scale_domain(x) + # Calculating the output + f = [[0]] + f[0] = [-((i[self.act_var[1]] - (5.1 / (4 * math.pi ** 2)) * i[self.act_var[0]] ** 2 + i[self.act_var[0]] * 5 / math.pi - 6) ** 2 + 10 * ( + 1 - 1 / (8 * math.pi)) * np.cos(i[self.act_var[0]]) + 10) for i in scaled_x] + f = np.transpose(f) + return f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class Hartmann6(TestFunction): + def __init__(self, act_var, noise_var=0): + self.range = np.array([[0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1], + [0, 1]]) + self.act_var = act_var + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self,x): + # Calculating the output + #Created on 08.09.2016 + # @author: Stefan Falkner + alpha = [1.00, 1.20, 3.00, 3.20] + A = np.array([[10.00, 3.00, 17.00, 3.50, 1.70, 8.00], + [0.05, 10.00, 17.00, 0.10, 8.00, 14.00], + [3.00, 3.50, 1.70, 10.00, 17.00, 8.00], + [17.00, 8.00, 0.05, 10.00, 0.10, 14.00]]) + P = 0.0001 * np.array([[1312, 1696, 5569, 124, 8283, 5886], + [2329, 4135, 8307, 3736, 1004, 9991], + [2348, 1451, 3522, 2883, 3047, 6650], + [4047, 8828, 8732, 5743, 1091, 381]]) + scaled_x = self.scale_domain(x) + n=len(scaled_x) + external_sum = np.zeros((n,1)) + for r in range(n): + for i in range(4): + internal_sum = 0 + for j in range(6): + internal_sum = internal_sum + A[i, j] * (scaled_x[r, self.act_var[j]] - P[i, j]) ** 2 + external_sum[r] = external_sum[r] + alpha[i] * np.exp(-internal_sum) + return external_sum + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class StybTang(TestFunction): + def __init__(self, act_var, noise_var=0): + D = len(act_var) + a = np.ones((D, 2)) + a = a * 5 + a[:, 0] = a[:, 0] * -1 + self.range = a + self.act_var=act_var + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i,1] - self.range[i,0]) / 2 + ( + self.range[i,1] + self.range[i,0]) / 2 + return x_copy + + def evaluate_true(self,x): + # Calculating the output + scaled_x=self.scale_domain(x) + f = [-0.5 * np.sum(np.power(scaled_x, 4) - 16 * np.power(scaled_x, 2) + 5 * scaled_x, axis=1)] + f = np.transpose(f) + print(f) + return f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) + +class Quadratic(TestFunction): + def __init__(self, act_var=None, noise_var=0): + self.range = np.array([[-1, 1], + [-1, 1]]) + if act_var is None: + self.act_var = np.arange(self.range.shape[0]) + else: + self.act_var = act_var + self.var = noise_var + + def scale_domain(self,x): + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i, 1] - self.range[i, 0]) / 2 + ( + self.range[i, 1] + self.range[i, 0]) / 2 + return x_copy + + def evaluate_true(self,x): + scaled_x = self.scale_domain(x) + f = [[0]] + f[0] = [-((i[self.act_var[0]]-1)**2+(i[self.act_var[1]]-1)**2) for i in scaled_x] + f = np.transpose(f) + return f + + def evaluate(self, x): + scaled_x = self.scale_domain(x) + n = len(scaled_x) + return self.evaluate_true(x) + np.random.normal(0,self.var,(n,1)) diff --git a/mylib/lib_REMBO/HesBO/kernel_inputs.py b/mylib/lib_REMBO/HesBO/kernel_inputs.py new file mode 100644 index 0000000..43e5c58 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/kernel_inputs.py @@ -0,0 +1,27 @@ +import numpy as np +import projections + +class InputY: + def __init__(self, A): + pass + + def evaluate(self, y): + return y + +class InputX(InputY): + def __init__(self, A): + super().__init__(A) + self.cp = projections.ConvexProjection(A) + + def evaluate(self, y): + x=self.cp.evaluate(y) + return x + +class InputPsi(InputY): + def __init__(self, A): + super().__init__(A) + self.wbp = projections.WarpingBackProjection(A) + + def evaluate(self, y): + psi=self.wbp.evaluate(y) + return psi diff --git a/mylib/lib_REMBO/HesBO/projection_matrix.py b/mylib/lib_REMBO/HesBO/projection_matrix.py new file mode 100644 index 0000000..26e61e9 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/projection_matrix.py @@ -0,0 +1,23 @@ +import numpy as np + +class SimpleGaussian: + def __init__(self,effective_dim,main_dim): + self.A = np.random.normal(0, 1, [effective_dim, main_dim]) + + def evaluate(self): + return self.A + +class Normalized(SimpleGaussian): + def evaluate(self): + effective_dim=len(self.A) + main_dim=len(self.A[0]) + new_matrix=np.zeros([effective_dim,main_dim]) + for i in range(effective_dim): + norm=np.linalg.norm(self.A[i]) + new_matrix[i]=self.A[i]/norm + return new_matrix + +class Orthogonalized(SimpleGaussian): + def evaluate(self): + pass + diff --git a/mylib/lib_REMBO/HesBO/projections.py b/mylib/lib_REMBO/HesBO/projections.py new file mode 100644 index 0000000..4c266f7 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/projections.py @@ -0,0 +1,48 @@ +import numpy as np + +class SimpleEmbedding: + def __init__(self, projection_matrix): + self.A=projection_matrix + + def evaluate(self, low_dim_vector): + # Multiplying A to y + x = np.matmul(low_dim_vector, self.A) + return x + +class ConvexProjection(SimpleEmbedding): + def evaluate(self, low_dim_vector): + x=SimpleEmbedding.evaluate(self, low_dim_vector) + # Projecting the values outside of X domain into the domain + n = len(x) + d = len(x[0]) + for i in range(n): + for j in range(d): + if x[i][j] > 1: x[i][j] = 1.0 + if x[i][j] < -1: x[i][j] = -1.0 + return x + +class WarpingBackProjection(ConvexProjection): + def __init__(self, projection_matrix): + ConvexProjection.__init__(self,projection_matrix) + org_bp_matrix = np.matmul(np.matmul(np.transpose(self.A), np.linalg.inv(np.matmul(self.A, np.transpose(self.A)))), self.A) + self.bp_matrix=np.transpose(org_bp_matrix) + + def evaluate(self, low_dim_vector): + x=ConvexProjection.evaluate(self, low_dim_vector) + n = len(x) + D = len(x[0]) + psi = np.empty((n, D)) + for i in range(n): + suitable_for_bp = False + for j in range(D): + if x[i][j]==1.0 or x[i][j]==-1.0: + suitable_for_bp = True + # Doing back projection for the points which are outside of the X domain + if suitable_for_bp: + z = np.matmul(x[i], self.bp_matrix) + max_z_abs = max(np.absolute(z)) + z_prime = z / max_z_abs + psi[i] = z_prime + np.linalg.norm(x[i] - z_prime, D) * (z_prime / np.linalg.norm(z_prime, D)) + else: + psi[i] = np.copy(x[i]) + return psi diff --git a/mylib/lib_REMBO/HesBO/requirements.txt b/mylib/lib_REMBO/HesBO/requirements.txt new file mode 100644 index 0000000..74763f0 --- /dev/null +++ b/mylib/lib_REMBO/HesBO/requirements.txt @@ -0,0 +1,3 @@ +numpy>=1.15.4 +scipy>=1.0.0 +GPy>=1.9.2 diff --git a/mylib/lib_turbo1/CONTRIBUTORS.md b/mylib/lib_turbo1/CONTRIBUTORS.md index 4fd0bc8..149abe2 100644 --- a/mylib/lib_turbo1/CONTRIBUTORS.md +++ b/mylib/lib_turbo1/CONTRIBUTORS.md @@ -1,2 +1,2 @@ -Code written by: -- David Eriksson +Code written by: +- David Eriksson diff --git a/mylib/lib_turbo1/LICENSE.md b/mylib/lib_turbo1/LICENSE.md index 488547e..063c3cb 100644 --- a/mylib/lib_turbo1/LICENSE.md +++ b/mylib/lib_turbo1/LICENSE.md @@ -1,41 +1,41 @@ -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by the text below. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under this License. - -This License governs use of the accompanying Work, and your use of the Work constitutes acceptance of this License. - -You may use this Work for any non-commercial purpose, subject to the restrictions in this License. Some purposes which can be non-commercial are teaching, academic research, and personal experimentation. You may also distribute this Work with books or other teaching materials, or publish the Work on websites, that are intended to teach the use of the Work. - -You may not use or distribute this Work, or any derivative works, outputs, or results from the Work, in any form for commercial purposes. Non-exhaustive examples of commercial purposes would be running business operations, licensing, leasing, or selling the Work, or distributing the Work for use with commercial products. - -You may modify this Work and distribute the modified Work for non-commercial purposes, however, you may not grant rights to the Work or derivative works that are broader than or in conflict with those provided by this License. For example, you may not distribute modifications of the Work under terms that would permit commercial use, or under terms that purport to require the Work or derivative works to be sublicensed to others. - -In return, we require that you agree: - -1. Not to remove any copyright or other notices from the Work. - -2. That if you distribute the Work in Source or Object form, you will include a verbatim copy of this License. - -3. That if you distribute derivative works of the Work in Source form, you do so only under a license that includes all of the provisions of this License and is not in conflict with this License, and if you distribute derivative works of the Work solely in Object form you do so only under a license that complies with this License. - -4. That if you have modified the Work or created derivative works from the Work, and distribute such modifications or derivative works, you will cause the modified files to carry prominent notices so that recipients know that they are not receiving the original Work. Such notices must state: (i) that you have changed the Work; and (ii) the date of any changes. - -5. If you publicly use the Work or any output or result of the Work, you will provide a notice with such use that provides any person who uses, views, accesses, interacts with, or is otherwise exposed to the Work (i) with information of the nature of the Work, (ii) with a link to the Work, and (iii) a notice that the Work is available under this License. - -6. THAT THE WORK COMES "AS IS", WITH NO WARRANTIES. THIS MEANS NO EXPRESS, IMPLIED OR STATUTORY WARRANTY, INCLUDING WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT. ALSO, YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. - -7. THAT NEITHER UBER TECHNOLOGIES, INC. NOR ANY OF ITS AFFILIATES, SUPPLIERS, SUCCESSORS, NOR ASSIGNS WILL BE LIABLE FOR ANY DAMAGES RELATED TO THE WORK OR THIS LICENSE, INCLUDING DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, TO THE MAXIMUM EXTENT THE LAW PERMITS, NO MATTER WHAT LEGAL THEORY IT IS BASED ON. ALSO, YOU MUST PASS THIS LIMITATION OF LIABILITY ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. - -8. That if you sue anyone over patents that you think may apply to the Work or anyone's use of the Work, your license to the Work ends automatically. - -9. That your rights under the License end automatically if you breach it in any way. - -10. Uber Technologies, Inc. reserves all rights not expressly granted to you in this License. +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by the text below. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under this License. + +This License governs use of the accompanying Work, and your use of the Work constitutes acceptance of this License. + +You may use this Work for any non-commercial purpose, subject to the restrictions in this License. Some purposes which can be non-commercial are teaching, academic research, and personal experimentation. You may also distribute this Work with books or other teaching materials, or publish the Work on websites, that are intended to teach the use of the Work. + +You may not use or distribute this Work, or any derivative works, outputs, or results from the Work, in any form for commercial purposes. Non-exhaustive examples of commercial purposes would be running business operations, licensing, leasing, or selling the Work, or distributing the Work for use with commercial products. + +You may modify this Work and distribute the modified Work for non-commercial purposes, however, you may not grant rights to the Work or derivative works that are broader than or in conflict with those provided by this License. For example, you may not distribute modifications of the Work under terms that would permit commercial use, or under terms that purport to require the Work or derivative works to be sublicensed to others. + +In return, we require that you agree: + +1. Not to remove any copyright or other notices from the Work. + +2. That if you distribute the Work in Source or Object form, you will include a verbatim copy of this License. + +3. That if you distribute derivative works of the Work in Source form, you do so only under a license that includes all of the provisions of this License and is not in conflict with this License, and if you distribute derivative works of the Work solely in Object form you do so only under a license that complies with this License. + +4. That if you have modified the Work or created derivative works from the Work, and distribute such modifications or derivative works, you will cause the modified files to carry prominent notices so that recipients know that they are not receiving the original Work. Such notices must state: (i) that you have changed the Work; and (ii) the date of any changes. + +5. If you publicly use the Work or any output or result of the Work, you will provide a notice with such use that provides any person who uses, views, accesses, interacts with, or is otherwise exposed to the Work (i) with information of the nature of the Work, (ii) with a link to the Work, and (iii) a notice that the Work is available under this License. + +6. THAT THE WORK COMES "AS IS", WITH NO WARRANTIES. THIS MEANS NO EXPRESS, IMPLIED OR STATUTORY WARRANTY, INCLUDING WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT. ALSO, YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +7. THAT NEITHER UBER TECHNOLOGIES, INC. NOR ANY OF ITS AFFILIATES, SUPPLIERS, SUCCESSORS, NOR ASSIGNS WILL BE LIABLE FOR ANY DAMAGES RELATED TO THE WORK OR THIS LICENSE, INCLUDING DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, TO THE MAXIMUM EXTENT THE LAW PERMITS, NO MATTER WHAT LEGAL THEORY IT IS BASED ON. ALSO, YOU MUST PASS THIS LIMITATION OF LIABILITY ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +8. That if you sue anyone over patents that you think may apply to the Work or anyone's use of the Work, your license to the Work ends automatically. + +9. That your rights under the License end automatically if you breach it in any way. + +10. Uber Technologies, Inc. reserves all rights not expressly granted to you in this License. diff --git a/mylib/lib_turbo1/README.md b/mylib/lib_turbo1/README.md index a49c27a..b0c3c98 100644 --- a/mylib/lib_turbo1/README.md +++ b/mylib/lib_turbo1/README.md @@ -1,95 +1,95 @@ -## Overview - -This is the code-release for the TuRBO algorithm from ***Scalable Global Optimization via Local Bayesian Optimization*** appearing in NeurIPS 2019. This is an implementation for the noise-free case and may not work well if observations are noisy as the center of the trust region should be chosen based on the posterior mean in this case. - -Note that TuRBO is a **minimization** algorithm, so please make sure you reformulate potential maximization problems. - -## Benchmark functions - -### Robot pushing -The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We have made the following changes to the code when running our experiments: - -1. We turned off the visualization, which speeds up the function evaluations. -2. We replaced all instances of ```np.random.normal(0, 0.01)``` by ```np.random.normal(0, 1e-6)``` in ```push_utils.py```. This makes the function close to noise-free. Another option is to average over several evaluations using the original code -3. We flipped the sign of the objective function to turn this into a minimization problem. - -Dependencies: ```numpy ```, ```pygame```, ```box2d-py``` - -### Rover -The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We used the large version of the problem, which has 60 dimensions. We have flipped the sign of the objective function to turn this into a minimization problem. - -Dependencies: ```numpy```, ```scipy``` - -### Lunar - -The lunar code is available in the OpenAI gym: https://github.com/openai/gym. The goal of the problem is to learn the parameter values of a controller for the lunar lander. The controller we learn is a modification of the original heuristic controller which takes the form: - -``` -def heuristic_Controller(s, w): - angle_targ = s[0] * w[0] + s[2] * w[1] - if angle_targ > w[2]: - angle_targ = w[2] - if angle_targ < -w[2]: - angle_targ = -w[2] - hover_targ = w[3] * np.abs(s[0]) - - angle_todo = (angle_targ - s[4]) * w[4] - (s[5]) * w[5] - hover_todo = (hover_targ - s[1]) * w[6] - (s[3]) * w[7] - - if s[6] or s[7]: - angle_todo = w[8] - hover_todo = -(s[3]) * w[9] - - a = 0 - if hover_todo > np.abs(angle_todo) and hover_todo > w[10]: - a = 2 - elif angle_todo < -w[11]: - a = 3 - elif angle_todo > +w[11]: - a = 1 - return a -``` - -We use the constraints 0 <= w_i <= 2 for all parameters. We use ```INITIAL_RANDOM = 1500.0``` to make the problem more challenging. - -For more information about the logic behind this controller and how to integrate it with ```gym```, take a look at the original heuristic controller source code: https://github.com/openai/gym/blob/master/gym/envs/box2d/lunar_lander.py#L364 - -Dependencies: ```gym```, ```box2d-py``` - -### Cosmological constant -The code for the cosmological constant problem is available here: https://ascl.net/1306.012. You need to follow the instructions and compile the FORTRAN code. This gives you an executable ```CAMB``` that you can call to run the simulation. - -The parameter names and bounds that we tune are the following: - -``` -ombh2: [0.01, 0.25] -omch2: [0.01, 0.25] -omnuh2: [0.01, 0.25] -omk: [0.01, 0.25] -hubble: [52.5, 100] -temp_cmb: [2.7, 2.8] -hefrac: [0.2, 0.3] -mneu: [2.9, 3.09] -scalar_amp: [1.5e-9, 2.6e-8] -scalar_spec_ind: [0.72, 5] -rf_fudge: [0, 100] -rf_fudge_he: [0, 100] -``` - -## Examples -Check the examples folder for two examples on how to use Turbo-1 and Turbo-n. - -## Citing us - -The final version of the paper is available at: http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization. - -``` -@inproceedings{eriksson2019scalable, - title = {Scalable Global Optimization via Local {Bayesian} Optimization}, - author = {Eriksson, David and Pearce, Michael and Gardner, Jacob and Turner, Ryan D and Poloczek, Matthias}, - booktitle = {Advances in Neural Information Processing Systems}, - pages = {5496--5507}, - year = {2019}, - url = {http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization.pdf}, -} -``` +## Overview + +This is the code-release for the TuRBO algorithm from ***Scalable Global Optimization via Local Bayesian Optimization*** appearing in NeurIPS 2019. This is an implementation for the noise-free case and may not work well if observations are noisy as the center of the trust region should be chosen based on the posterior mean in this case. + +Note that TuRBO is a **minimization** algorithm, so please make sure you reformulate potential maximization problems. + +## Benchmark functions + +### Robot pushing +The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We have made the following changes to the code when running our experiments: + +1. We turned off the visualization, which speeds up the function evaluations. +2. We replaced all instances of ```np.random.normal(0, 0.01)``` by ```np.random.normal(0, 1e-6)``` in ```push_utils.py```. This makes the function close to noise-free. Another option is to average over several evaluations using the original code +3. We flipped the sign of the objective function to turn this into a minimization problem. + +Dependencies: ```numpy ```, ```pygame```, ```box2d-py``` + +### Rover +The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We used the large version of the problem, which has 60 dimensions. We have flipped the sign of the objective function to turn this into a minimization problem. + +Dependencies: ```numpy```, ```scipy``` + +### Lunar + +The lunar code is available in the OpenAI gym: https://github.com/openai/gym. The goal of the problem is to learn the parameter values of a controller for the lunar lander. The controller we learn is a modification of the original heuristic controller which takes the form: + +``` +def heuristic_Controller(s, w): + angle_targ = s[0] * w[0] + s[2] * w[1] + if angle_targ > w[2]: + angle_targ = w[2] + if angle_targ < -w[2]: + angle_targ = -w[2] + hover_targ = w[3] * np.abs(s[0]) + + angle_todo = (angle_targ - s[4]) * w[4] - (s[5]) * w[5] + hover_todo = (hover_targ - s[1]) * w[6] - (s[3]) * w[7] + + if s[6] or s[7]: + angle_todo = w[8] + hover_todo = -(s[3]) * w[9] + + a = 0 + if hover_todo > np.abs(angle_todo) and hover_todo > w[10]: + a = 2 + elif angle_todo < -w[11]: + a = 3 + elif angle_todo > +w[11]: + a = 1 + return a +``` + +We use the constraints 0 <= w_i <= 2 for all parameters. We use ```INITIAL_RANDOM = 1500.0``` to make the problem more challenging. + +For more information about the logic behind this controller and how to integrate it with ```gym```, take a look at the original heuristic controller source code: https://github.com/openai/gym/blob/master/gym/envs/box2d/lunar_lander.py#L364 + +Dependencies: ```gym```, ```box2d-py``` + +### Cosmological constant +The code for the cosmological constant problem is available here: https://ascl.net/1306.012. You need to follow the instructions and compile the FORTRAN code. This gives you an executable ```CAMB``` that you can call to run the simulation. + +The parameter names and bounds that we tune are the following: + +``` +ombh2: [0.01, 0.25] +omch2: [0.01, 0.25] +omnuh2: [0.01, 0.25] +omk: [0.01, 0.25] +hubble: [52.5, 100] +temp_cmb: [2.7, 2.8] +hefrac: [0.2, 0.3] +mneu: [2.9, 3.09] +scalar_amp: [1.5e-9, 2.6e-8] +scalar_spec_ind: [0.72, 5] +rf_fudge: [0, 100] +rf_fudge_he: [0, 100] +``` + +## Examples +Check the examples folder for two examples on how to use Turbo-1 and Turbo-n. + +## Citing us + +The final version of the paper is available at: http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization. + +``` +@inproceedings{eriksson2019scalable, + title = {Scalable Global Optimization via Local {Bayesian} Optimization}, + author = {Eriksson, David and Pearce, Michael and Gardner, Jacob and Turner, Ryan D and Poloczek, Matthias}, + booktitle = {Advances in Neural Information Processing Systems}, + pages = {5496--5507}, + year = {2019}, + url = {http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization.pdf}, +} +``` diff --git a/mylib/lib_turbo1/examples/Turbo1.ipynb b/mylib/lib_turbo1/examples/Turbo1.ipynb index 7226a00..23004bd 100644 --- a/mylib/lib_turbo1/examples/Turbo1.ipynb +++ b/mylib/lib_turbo1/examples/Turbo1.ipynb @@ -1,254 +1,254 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simple example of TuRBO-1" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from turbo import Turbo1\n", - "import numpy as np\n", - "import torch\n", - "import math\n", - "import matplotlib\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up an optimization problem class" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "class Levy:\n", - " def __init__(self, dim=10):\n", - " self.dim = dim\n", - " self.lb = -5 * np.ones(dim)\n", - " self.ub = 10 * np.ones(dim)\n", - " \n", - " def __call__(self, x):\n", - " assert len(x) == self.dim\n", - " assert x.ndim == 1\n", - " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", - " w = 1 + (x - 1.0) / 4.0\n", - " val = np.sin(np.pi * w[0]) ** 2 + \\\n", - " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", - " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", - " return val\n", - "\n", - "f = Levy(10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a Turbo optimizer instance" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using dtype = torch.float64 \n", - "Using device = cpu\n" - ] - } - ], - "source": [ - "turbo1 = Turbo1(\n", - " f=f, # Handle to objective function\n", - " lb=f.lb, # Numpy array specifying lower bounds\n", - " ub=f.ub, # Numpy array specifying upper bounds\n", - " n_init=20, # Number of initial bounds from an Latin hypercube design\n", - " max_evals = 1000, # Maximum number of evaluations\n", - " batch_size=10, # How large batch size TuRBO uses\n", - " verbose=True, # Print information from each batch\n", - " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", - " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", - " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", - " min_cuda=1024, # Run on the CPU for small datasets\n", - " device=\"cpu\", # \"cpu\" or \"cuda\"\n", - " dtype=\"float64\", # float64 or float32\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Run the optimization process" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starting from fbest = 20.98\n", - "50) New best: 15.65\n", - "80) New best: 11.27\n", - "90) New best: 9.325\n", - "100) New best: 8.288\n", - "110) New best: 6.944\n", - "120) New best: 5.974\n", - "140) New best: 5.951\n", - "160) New best: 5.905\n", - "170) New best: 5.905\n", - "180) New best: 5.822\n", - "190) New best: 5.785\n", - "200) New best: 5.759\n", - "220) New best: 5.738\n", - "230) New best: 5.683\n", - "240) Restarting with fbest = 5.683\n", - "Starting from fbest = 32.5\n", - "320) New best: 5.526\n", - "330) New best: 3.95\n", - "350) New best: 1.736\n", - "370) New best: 1.229\n", - "410) New best: 1.206\n", - "420) New best: 1.193\n", - "430) New best: 1.191\n", - "440) New best: 1.163\n", - "450) New best: 1.145\n", - "460) New best: 1.06\n", - "480) New best: 1.024\n", - "490) New best: 1.01\n", - "500) New best: 1.001\n", - "530) Restarting with fbest = 1.001\n", - "Starting from fbest = 12.85\n", - "730) Restarting with fbest = 8.634\n", - "Starting from fbest = 9.62\n", - "890) Restarting with fbest = 5.87\n", - "Starting from fbest = 25.71\n" - ] - } - ], - "source": [ - "turbo1.optimize()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extract all evaluations from Turbo and print the best" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Best value found:\n", - "\tf(x) = 1.001\n", - "Observed at:\n", - "\tx = [-3.006 0.914 3.659 0.853 0.033 -0.203 1.199 0.812 -0.301 2.42 ]\n" - ] - } - ], - "source": [ - "X = turbo1.X # Evaluated points\n", - "fX = turbo1.fX # Observed values\n", - "ind_best = np.argmin(fX)\n", - "f_best, x_best = fX[ind_best], X[ind_best, :]\n", - "\n", - "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plot the progress\n", - "Each trust region is independent and finds different solutions" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "fig = plt.figure(figsize=(7, 5))\n", - "matplotlib.rcParams.update({'font.size': 16})\n", - "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", - "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", - "plt.xlim([0, len(fX)])\n", - "plt.ylim([0, 30])\n", - "plt.title(\"10D Levy function\")\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple example of TuRBO-1" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from turbo import Turbo1\n", + "import numpy as np\n", + "import torch\n", + "import math\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up an optimization problem class" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class Levy:\n", + " def __init__(self, dim=10):\n", + " self.dim = dim\n", + " self.lb = -5 * np.ones(dim)\n", + " self.ub = 10 * np.ones(dim)\n", + " \n", + " def __call__(self, x):\n", + " assert len(x) == self.dim\n", + " assert x.ndim == 1\n", + " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", + " w = 1 + (x - 1.0) / 4.0\n", + " val = np.sin(np.pi * w[0]) ** 2 + \\\n", + " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", + " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", + " return val\n", + "\n", + "f = Levy(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Turbo optimizer instance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using dtype = torch.float64 \n", + "Using device = cpu\n" + ] + } + ], + "source": [ + "turbo1 = Turbo1(\n", + " f=f, # Handle to objective function\n", + " lb=f.lb, # Numpy array specifying lower bounds\n", + " ub=f.ub, # Numpy array specifying upper bounds\n", + " n_init=20, # Number of initial bounds from an Latin hypercube design\n", + " max_evals = 1000, # Maximum number of evaluations\n", + " batch_size=10, # How large batch size TuRBO uses\n", + " verbose=True, # Print information from each batch\n", + " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", + " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", + " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", + " min_cuda=1024, # Run on the CPU for small datasets\n", + " device=\"cpu\", # \"cpu\" or \"cuda\"\n", + " dtype=\"float64\", # float64 or float32\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run the optimization process" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting from fbest = 20.98\n", + "50) New best: 15.65\n", + "80) New best: 11.27\n", + "90) New best: 9.325\n", + "100) New best: 8.288\n", + "110) New best: 6.944\n", + "120) New best: 5.974\n", + "140) New best: 5.951\n", + "160) New best: 5.905\n", + "170) New best: 5.905\n", + "180) New best: 5.822\n", + "190) New best: 5.785\n", + "200) New best: 5.759\n", + "220) New best: 5.738\n", + "230) New best: 5.683\n", + "240) Restarting with fbest = 5.683\n", + "Starting from fbest = 32.5\n", + "320) New best: 5.526\n", + "330) New best: 3.95\n", + "350) New best: 1.736\n", + "370) New best: 1.229\n", + "410) New best: 1.206\n", + "420) New best: 1.193\n", + "430) New best: 1.191\n", + "440) New best: 1.163\n", + "450) New best: 1.145\n", + "460) New best: 1.06\n", + "480) New best: 1.024\n", + "490) New best: 1.01\n", + "500) New best: 1.001\n", + "530) Restarting with fbest = 1.001\n", + "Starting from fbest = 12.85\n", + "730) Restarting with fbest = 8.634\n", + "Starting from fbest = 9.62\n", + "890) Restarting with fbest = 5.87\n", + "Starting from fbest = 25.71\n" + ] + } + ], + "source": [ + "turbo1.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract all evaluations from Turbo and print the best" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best value found:\n", + "\tf(x) = 1.001\n", + "Observed at:\n", + "\tx = [-3.006 0.914 3.659 0.853 0.033 -0.203 1.199 0.812 -0.301 2.42 ]\n" + ] + } + ], + "source": [ + "X = turbo1.X # Evaluated points\n", + "fX = turbo1.fX # Observed values\n", + "ind_best = np.argmin(fX)\n", + "f_best, x_best = fX[ind_best], X[ind_best, :]\n", + "\n", + "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot the progress\n", + "Each trust region is independent and finds different solutions" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(7, 5))\n", + "matplotlib.rcParams.update({'font.size': 16})\n", + "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", + "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", + "plt.xlim([0, len(fX)])\n", + "plt.ylim([0, 30])\n", + "plt.title(\"10D Levy function\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/mylib/lib_turbo1/examples/TurboM.ipynb b/mylib/lib_turbo1/examples/TurboM.ipynb index c5a632c..15e350e 100644 --- a/mylib/lib_turbo1/examples/TurboM.ipynb +++ b/mylib/lib_turbo1/examples/TurboM.ipynb @@ -1,247 +1,247 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simple example of TuRBO-m" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from turbo import TurboM\n", - "import numpy as np\n", - "import torch\n", - "import math\n", - "import matplotlib\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up an optimization problem class" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "class Levy:\n", - " def __init__(self, dim=10):\n", - " self.dim = dim\n", - " self.lb = -5 * np.ones(dim)\n", - " self.ub = 10 * np.ones(dim)\n", - " \n", - " def __call__(self, x):\n", - " assert len(x) == self.dim\n", - " assert x.ndim == 1\n", - " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", - " w = 1 + (x - 1.0) / 4.0\n", - " val = np.sin(np.pi * w[0]) ** 2 + \\\n", - " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", - " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", - " return val\n", - "\n", - "f = Levy(10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a Turbo optimizer instance" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using dtype = torch.float64 \n", - "Using device = cpu\n" - ] - } - ], - "source": [ - "turbo_m = TurboM(\n", - " f=f, # Handle to objective function\n", - " lb=f.lb, # Numpy array specifying lower bounds\n", - " ub=f.ub, # Numpy array specifying upper bounds\n", - " n_init=10, # Number of initial bounds from an Symmetric Latin hypercube design\n", - " max_evals=1000, # Maximum number of evaluations\n", - " n_trust_regions=5, # Number of trust regions\n", - " batch_size=10, # How large batch size TuRBO uses\n", - " verbose=True, # Print information from each batch\n", - " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", - " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", - " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", - " min_cuda=1024, # Run on the CPU for small datasets\n", - " device=\"cpu\", # \"cpu\" or \"cuda\"\n", - " dtype=\"float64\", # float64 or float32\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Run the optimization process" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TR-0 starting from: 24.79\n", - "TR-1 starting from: 20.77\n", - "TR-2 starting from: 14.87\n", - "TR-3 starting from: 27.97\n", - "TR-4 starting from: 23.89\n", - "80) New best @ TR-2: 12.43\n", - "90) New best @ TR-2: 6.42\n", - "110) New best @ TR-2: 5.467\n", - "180) New best @ TR-2: 2.888\n", - "230) New best @ TR-1: 1.944\n", - "280) New best @ TR-1: 1.54\n", - "310) New best @ TR-1: 1.052\n", - "340) New best @ TR-1: 1.038\n", - "390) New best @ TR-1: 0.9689\n", - "410) New best @ TR-1: 0.877\n", - "420) New best @ TR-1: 0.7794\n", - "460) New best @ TR-1: 0.7509\n", - "470) New best @ TR-1: 0.7264\n", - "480) New best @ TR-1: 0.7238\n", - "530) New best @ TR-1: 0.7044\n", - "540) New best @ TR-1: 0.695\n", - "550) New best @ TR-1: 0.6823\n", - "560) New best @ TR-1: 0.6656\n", - "590) New best @ TR-1: 0.6614\n", - "600) New best @ TR-1: 0.6604\n", - "640) TR-1 converged to: : 0.6604\n", - "640) TR-1 is restarting from: : 23.66\n" - ] - } - ], - "source": [ - "turbo_m.optimize()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extract all evaluations from Turbo and print the best" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Best value found:\n", - "\tf(x) = 0.660\n", - "Observed at:\n", - "\tx = [-2.968 1.072 0.173 0.973 3.698 0.883 0.946 0.872 0.006 0.927]\n" - ] - } - ], - "source": [ - "X = turbo_m.X # Evaluated points\n", - "fX = turbo_m.fX # Observed values\n", - "ind_best = np.argmin(fX)\n", - "f_best, x_best = fX[ind_best], X[ind_best, :]\n", - "\n", - "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plot the progress\n", - "\n", - "TuRBO-5 converges to a solution close to the global optimum" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "fig = plt.figure(figsize=(7, 5))\n", - "matplotlib.rcParams.update({'font.size': 16})\n", - "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", - "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", - "plt.xlim([0, len(fX)])\n", - "plt.ylim([0, 30])\n", - "plt.title(\"10D Levy function\")\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple example of TuRBO-m" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from turbo import TurboM\n", + "import numpy as np\n", + "import torch\n", + "import math\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up an optimization problem class" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class Levy:\n", + " def __init__(self, dim=10):\n", + " self.dim = dim\n", + " self.lb = -5 * np.ones(dim)\n", + " self.ub = 10 * np.ones(dim)\n", + " \n", + " def __call__(self, x):\n", + " assert len(x) == self.dim\n", + " assert x.ndim == 1\n", + " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", + " w = 1 + (x - 1.0) / 4.0\n", + " val = np.sin(np.pi * w[0]) ** 2 + \\\n", + " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", + " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", + " return val\n", + "\n", + "f = Levy(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Turbo optimizer instance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using dtype = torch.float64 \n", + "Using device = cpu\n" + ] + } + ], + "source": [ + "turbo_m = TurboM(\n", + " f=f, # Handle to objective function\n", + " lb=f.lb, # Numpy array specifying lower bounds\n", + " ub=f.ub, # Numpy array specifying upper bounds\n", + " n_init=10, # Number of initial bounds from an Symmetric Latin hypercube design\n", + " max_evals=1000, # Maximum number of evaluations\n", + " n_trust_regions=5, # Number of trust regions\n", + " batch_size=10, # How large batch size TuRBO uses\n", + " verbose=True, # Print information from each batch\n", + " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", + " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", + " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", + " min_cuda=1024, # Run on the CPU for small datasets\n", + " device=\"cpu\", # \"cpu\" or \"cuda\"\n", + " dtype=\"float64\", # float64 or float32\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run the optimization process" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TR-0 starting from: 24.79\n", + "TR-1 starting from: 20.77\n", + "TR-2 starting from: 14.87\n", + "TR-3 starting from: 27.97\n", + "TR-4 starting from: 23.89\n", + "80) New best @ TR-2: 12.43\n", + "90) New best @ TR-2: 6.42\n", + "110) New best @ TR-2: 5.467\n", + "180) New best @ TR-2: 2.888\n", + "230) New best @ TR-1: 1.944\n", + "280) New best @ TR-1: 1.54\n", + "310) New best @ TR-1: 1.052\n", + "340) New best @ TR-1: 1.038\n", + "390) New best @ TR-1: 0.9689\n", + "410) New best @ TR-1: 0.877\n", + "420) New best @ TR-1: 0.7794\n", + "460) New best @ TR-1: 0.7509\n", + "470) New best @ TR-1: 0.7264\n", + "480) New best @ TR-1: 0.7238\n", + "530) New best @ TR-1: 0.7044\n", + "540) New best @ TR-1: 0.695\n", + "550) New best @ TR-1: 0.6823\n", + "560) New best @ TR-1: 0.6656\n", + "590) New best @ TR-1: 0.6614\n", + "600) New best @ TR-1: 0.6604\n", + "640) TR-1 converged to: : 0.6604\n", + "640) TR-1 is restarting from: : 23.66\n" + ] + } + ], + "source": [ + "turbo_m.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract all evaluations from Turbo and print the best" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best value found:\n", + "\tf(x) = 0.660\n", + "Observed at:\n", + "\tx = [-2.968 1.072 0.173 0.973 3.698 0.883 0.946 0.872 0.006 0.927]\n" + ] + } + ], + "source": [ + "X = turbo_m.X # Evaluated points\n", + "fX = turbo_m.fX # Observed values\n", + "ind_best = np.argmin(fX)\n", + "f_best, x_best = fX[ind_best], X[ind_best, :]\n", + "\n", + "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot the progress\n", + "\n", + "TuRBO-5 converges to a solution close to the global optimum" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(7, 5))\n", + "matplotlib.rcParams.update({'font.size': 16})\n", + "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", + "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", + "plt.xlim([0, len(fX)])\n", + "plt.ylim([0, 30])\n", + "plt.title(\"10D Levy function\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/mylib/lib_turbo1/requirements.txt b/mylib/lib_turbo1/requirements.txt index 525eb63..8aea783 100644 --- a/mylib/lib_turbo1/requirements.txt +++ b/mylib/lib_turbo1/requirements.txt @@ -1,3 +1,3 @@ -numpy==1.17.3 -torch==1.3.0 -gpytorch==0.3.6 +numpy==1.17.3 +torch==1.3.0 +gpytorch==0.3.6 diff --git a/mylib/lib_turbo1/setup.py b/mylib/lib_turbo1/setup.py index bd5afd5..d1c7647 100644 --- a/mylib/lib_turbo1/setup.py +++ b/mylib/lib_turbo1/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup, find_packages - -setup( - name="turbo", - version="0.0.1", - packages=find_packages(), - install_requires=["numpy>=1.17.3", "torch>=1.3.0", "gpytorch>=0.3.6"], -) +from setuptools import setup, find_packages + +setup( + name="turbo", + version="0.0.1", + packages=find_packages(), + install_requires=["numpy>=1.17.3", "torch>=1.3.0", "gpytorch>=0.3.6"], +) diff --git a/mylib/lib_turbo1/turbo/__init__.py b/mylib/lib_turbo1/turbo/__init__.py index 6d17f20..eb0846a 100644 --- a/mylib/lib_turbo1/turbo/__init__.py +++ b/mylib/lib_turbo1/turbo/__init__.py @@ -1,2 +1,2 @@ -from .turbo_1 import Turbo1 -from .turbo_m import TurboM +from .turbo_1 import Turbo1 +from .turbo_m import TurboM diff --git a/mylib/lib_turbo1/turbo/__pycache__/__init__.cpython-310.pyc b/mylib/lib_turbo1/turbo/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..cbff016 Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/__init__.cpython-310.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/__init__.cpython-38.pyc b/mylib/lib_turbo1/turbo/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..19e624c Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/__init__.cpython-38.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/gp.cpython-310.pyc b/mylib/lib_turbo1/turbo/__pycache__/gp.cpython-310.pyc new file mode 100644 index 0000000..bb9f63f Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/gp.cpython-310.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/gp.cpython-38.pyc b/mylib/lib_turbo1/turbo/__pycache__/gp.cpython-38.pyc new file mode 100644 index 0000000..4b69446 Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/gp.cpython-38.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/turbo_1.cpython-310.pyc b/mylib/lib_turbo1/turbo/__pycache__/turbo_1.cpython-310.pyc new file mode 100644 index 0000000..087ff08 Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/turbo_1.cpython-310.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/turbo_1.cpython-38.pyc b/mylib/lib_turbo1/turbo/__pycache__/turbo_1.cpython-38.pyc new file mode 100644 index 0000000..493556f Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/turbo_1.cpython-38.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/turbo_m.cpython-310.pyc b/mylib/lib_turbo1/turbo/__pycache__/turbo_m.cpython-310.pyc new file mode 100644 index 0000000..8d0b58f Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/turbo_m.cpython-310.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/turbo_m.cpython-38.pyc b/mylib/lib_turbo1/turbo/__pycache__/turbo_m.cpython-38.pyc new file mode 100644 index 0000000..d18cb19 Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/turbo_m.cpython-38.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/utils.cpython-310.pyc b/mylib/lib_turbo1/turbo/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..938c787 Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/utils.cpython-310.pyc differ diff --git a/mylib/lib_turbo1/turbo/__pycache__/utils.cpython-38.pyc b/mylib/lib_turbo1/turbo/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..bf3398a Binary files /dev/null and b/mylib/lib_turbo1/turbo/__pycache__/utils.cpython-38.pyc differ diff --git a/mylib/lib_turbo1/turbo/gp.py b/mylib/lib_turbo1/turbo/gp.py index 873df9d..9f1b208 100644 --- a/mylib/lib_turbo1/turbo/gp.py +++ b/mylib/lib_turbo1/turbo/gp.py @@ -1,98 +1,98 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import math - -import gpytorch -import numpy as np -import torch -from gpytorch.constraints.constraints import Interval -from gpytorch.distributions import MultivariateNormal -from gpytorch.kernels import MaternKernel, ScaleKernel -from gpytorch.likelihoods import GaussianLikelihood -from gpytorch.means import ConstantMean -from gpytorch.mlls import ExactMarginalLogLikelihood -from gpytorch.models import ExactGP - - -# GP Model -class GP(ExactGP): - def __init__(self, train_x, train_y, likelihood, lengthscale_constraint, outputscale_constraint, ard_dims): - super(GP, self).__init__(train_x, train_y, likelihood) - self.ard_dims = ard_dims - self.mean_module = ConstantMean() - base_kernel = MaternKernel(lengthscale_constraint=lengthscale_constraint, ard_num_dims=ard_dims, nu=2.5) - self.covar_module = ScaleKernel(base_kernel, outputscale_constraint=outputscale_constraint) - - def forward(self, x): - mean_x = self.mean_module(x) - covar_x = self.covar_module(x) - return MultivariateNormal(mean_x, covar_x) - - -def train_gp(train_x, train_y, use_ard, num_steps, hypers={}): - """Fit a GP model where train_x is in [0, 1]^d and train_y is standardized.""" - assert train_x.ndim == 2 - assert train_y.ndim == 1 - assert train_x.shape[0] == train_y.shape[0] - - # Create hyper parameter bounds - noise_constraint = Interval(5e-4, 0.2) - if use_ard: - lengthscale_constraint = Interval(0.005, 2.0) - else: - lengthscale_constraint = Interval(0.005, math.sqrt(train_x.shape[1])) # [0.005, sqrt(dim)] - outputscale_constraint = Interval(0.05, 20.0) - - # Create models - likelihood = GaussianLikelihood(noise_constraint=noise_constraint).to(device=train_x.device, dtype=train_y.dtype) - ard_dims = train_x.shape[1] if use_ard else None - model = GP( - train_x=train_x, - train_y=train_y, - likelihood=likelihood, - lengthscale_constraint=lengthscale_constraint, - outputscale_constraint=outputscale_constraint, - ard_dims=ard_dims, - ).to(device=train_x.device, dtype=train_x.dtype) - - # Find optimal model hyperparameters - model.train() - likelihood.train() - - # "Loss" for GPs - the marginal log likelihood - mll = ExactMarginalLogLikelihood(likelihood, model) - - # Initialize model hypers - if hypers: - model.load_state_dict(hypers) - else: - hypers = {} - hypers["covar_module.outputscale"] = 1.0 - hypers["covar_module.base_kernel.lengthscale"] = 0.5 - hypers["likelihood.noise"] = 0.005 - model.initialize(**hypers) - - # Use the adam optimizer - optimizer = torch.optim.Adam([{"params": model.parameters()}], lr=0.1) - - for _ in range(num_steps): - optimizer.zero_grad() - output = model(train_x) - loss = -mll(output, train_y) - loss.backward() - optimizer.step() - - # Switch to eval mode - model.eval() - likelihood.eval() - - return model +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math + +import gpytorch +import numpy as np +import torch +from gpytorch.constraints.constraints import Interval +from gpytorch.distributions import MultivariateNormal +from gpytorch.kernels import MaternKernel, ScaleKernel +from gpytorch.likelihoods import GaussianLikelihood +from gpytorch.means import ConstantMean +from gpytorch.mlls import ExactMarginalLogLikelihood +from gpytorch.models import ExactGP + + +# GP Model +class GP(ExactGP): + def __init__(self, train_x, train_y, likelihood, lengthscale_constraint, outputscale_constraint, ard_dims): + super(GP, self).__init__(train_x, train_y, likelihood) + self.ard_dims = ard_dims + self.mean_module = ConstantMean() + base_kernel = MaternKernel(lengthscale_constraint=lengthscale_constraint, ard_num_dims=ard_dims, nu=2.5) + self.covar_module = ScaleKernel(base_kernel, outputscale_constraint=outputscale_constraint) + + def forward(self, x): + mean_x = self.mean_module(x) + covar_x = self.covar_module(x) + return MultivariateNormal(mean_x, covar_x) + + +def train_gp(train_x, train_y, use_ard, num_steps, hypers={}): + """Fit a GP model where train_x is in [0, 1]^d and train_y is standardized.""" + assert train_x.ndim == 2 + assert train_y.ndim == 1 + assert train_x.shape[0] == train_y.shape[0] + + # Create hyper parameter bounds + noise_constraint = Interval(5e-4, 0.2) + if use_ard: + lengthscale_constraint = Interval(0.005, 2.0) + else: + lengthscale_constraint = Interval(0.005, math.sqrt(train_x.shape[1])) # [0.005, sqrt(dim)] + outputscale_constraint = Interval(0.05, 20.0) + + # Create models + likelihood = GaussianLikelihood(noise_constraint=noise_constraint).to(device=train_x.device, dtype=train_y.dtype) + ard_dims = train_x.shape[1] if use_ard else None + model = GP( + train_x=train_x, + train_y=train_y, + likelihood=likelihood, + lengthscale_constraint=lengthscale_constraint, + outputscale_constraint=outputscale_constraint, + ard_dims=ard_dims, + ).to(device=train_x.device, dtype=train_x.dtype) + + # Find optimal model hyperparameters + model.train() + likelihood.train() + + # "Loss" for GPs - the marginal log likelihood + mll = ExactMarginalLogLikelihood(likelihood, model) + + # Initialize model hypers + if hypers: + model.load_state_dict(hypers) + else: + hypers = {} + hypers["covar_module.outputscale"] = 1.0 + hypers["covar_module.base_kernel.lengthscale"] = 0.5 + hypers["likelihood.noise"] = 0.005 + model.initialize(**hypers) + + # Use the adam optimizer + optimizer = torch.optim.Adam([{"params": model.parameters()}], lr=0.1) + + for _ in range(num_steps): + optimizer.zero_grad() + output = model(train_x) + loss = -mll(output, train_y) + loss.backward() + optimizer.step() + + # Switch to eval mode + model.eval() + likelihood.eval() + + return model diff --git a/mylib/lib_turbo1/turbo/turbo_1.py b/mylib/lib_turbo1/turbo/turbo_1.py index fff8724..98c3f1b 100644 --- a/mylib/lib_turbo1/turbo/turbo_1.py +++ b/mylib/lib_turbo1/turbo/turbo_1.py @@ -1,312 +1,322 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import math -import sys -import time -from copy import deepcopy - -import gpytorch -import numpy as np -import torch -from torch.quasirandom import SobolEngine - -from .gp import train_gp -from .utils import from_unit_cube, latin_hypercube, to_unit_cube - - -class Turbo1: - """The TuRBO-1 algorithm. - - Parameters - ---------- - f : function handle - lb : Lower variable bounds, numpy.array, shape (d,). - ub : Upper variable bounds, numpy.array, shape (d,). - n_init : Number of initial points (2*dim is recommended), int. - max_evals : Total evaluation budget, int. - batch_size : Number of points in each batch, int. - verbose : If you want to print information about the optimization progress, bool. - use_ard : If you want to use ARD for the GP kernel. - max_cholesky_size : Largest number of training points where we use Cholesky, int - n_training_steps : Number of training steps for learning the GP hypers, int - min_cuda : We use float64 on the CPU if we have this or fewer datapoints - device : Device to use for GP fitting ("cpu" or "cuda") - dtype : Dtype to use for GP fitting ("float32" or "float64") - - Example usage: - turbo1 = Turbo1(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals) - turbo1.optimize() # Run optimization - X, fX = turbo1.X, turbo1.fX # Evaluated points - """ - - def __init__( - self, - f, - lb, - ub, - n_init, - max_evals, - batch_size=1, - verbose=True, - use_ard=True, - max_cholesky_size=2000, - n_training_steps=50, - min_cuda=1024, - device="cpu", - dtype="float64", - ): - - # Very basic input checks - assert lb.ndim == 1 and ub.ndim == 1 - assert len(lb) == len(ub) - assert np.all(ub > lb) - assert max_evals > 0 and isinstance(max_evals, int) - assert n_init > 0 and isinstance(n_init, int) - assert batch_size > 0 and isinstance(batch_size, int) - assert isinstance(verbose, bool) and isinstance(use_ard, bool) - assert max_cholesky_size >= 0 and isinstance(batch_size, int) - assert n_training_steps >= 30 and isinstance(n_training_steps, int) - assert max_evals > n_init and max_evals > batch_size - assert device == "cpu" or device == "cuda" - assert dtype == "float32" or dtype == "float64" - if device == "cuda": - assert torch.cuda.is_available(), "can't use cuda if it's not available" - - # Save function information - self.f = f - self.dim = len(lb) - self.lb = lb - self.ub = ub - - # Settings - self.n_init = n_init - self.max_evals = max_evals - self.batch_size = batch_size - self.verbose = verbose - self.use_ard = use_ard - self.max_cholesky_size = max_cholesky_size - self.n_training_steps = n_training_steps - - # Hyperparameters - self.mean = np.zeros((0, 1)) - self.signal_var = np.zeros((0, 1)) - self.noise_var = np.zeros((0, 1)) - self.lengthscales = np.zeros((0, self.dim)) if self.use_ard else np.zeros((0, 1)) - - # Tolerances and counters - self.n_cand = min(100 * self.dim, 5000) - self.failtol = np.ceil(np.max([4.0 / batch_size, self.dim / batch_size])) - self.succtol = 3 - self.n_evals = 0 - - # Trust region sizes - self.length_min = 0.5 ** 7 - self.length_max = 1.6 - self.length_init = 0.8 - - # Save the full history - self.X = np.zeros((0, self.dim)) - self.fX = np.zeros((0, 1)) - - # Device and dtype for GPyTorch - self.min_cuda = min_cuda - self.dtype = torch.float32 if dtype == "float32" else torch.float64 - self.device = torch.device("cuda") if device == "cuda" else torch.device("cpu") - if self.verbose: - print("Using dtype = %s \nUsing device = %s" % (self.dtype, self.device)) - sys.stdout.flush() - - # Initialize parameters - self._restart() - - self.acq_opt_time = 0 - self.mode_fit_time = 0 - self.cum_iteration_time = 0 - - def _restart(self): - self._X = [] - self._fX = [] - self.failcount = 0 - self.succcount = 0 - self.length = self.length_init - - def _adjust_length(self, fX_next): - if np.min(fX_next) < np.min(self._fX) - 1e-3 * math.fabs(np.min(self._fX)): - self.succcount += 1 - self.failcount = 0 - else: - self.succcount = 0 - self.failcount += 1 - - if self.succcount == self.succtol: # Expand trust region - self.length = min([2.0 * self.length, self.length_max]) - self.succcount = 0 - elif self.failcount == self.failtol: # Shrink trust region - self.length /= 2.0 - self.failcount = 0 - - def _create_candidates(self, X, fX, length, n_training_steps, hypers): - """Generate candidates assuming X has been scaled to [0,1]^d.""" - # Pick the center as the point with the smallest function values - # NOTE: This may not be robust to noise, in which case the posterior mean of the GP can be used instead - assert X.min() >= 0.0 and X.max() <= 1.0 - - # Standardize function values. - mu, sigma = np.median(fX), fX.std() - sigma = 1.0 if sigma < 1e-6 else sigma - fX = (deepcopy(fX) - mu) / sigma - - # Figure out what device we are running on - if len(X) < self.min_cuda: - device, dtype = torch.device("cpu"), torch.float64 - else: - device, dtype = self.device, self.dtype - - # We use CG + Lanczos for training if we have enough data - with gpytorch.settings.max_cholesky_size(self.max_cholesky_size): - X_torch = torch.tensor(X).to(device=device, dtype=dtype) - y_torch = torch.tensor(fX).to(device=device, dtype=dtype) - gp = train_gp( - train_x=X_torch, train_y=y_torch, use_ard=self.use_ard, num_steps=n_training_steps, hypers=hypers - ) - - # Save state dict - hypers = gp.state_dict() - - # Create the trust region boundaries - x_center = X[fX.argmin().item(), :][None, :] - weights = gp.covar_module.base_kernel.lengthscale.cpu().detach().numpy().ravel() - weights = weights / weights.mean() # This will make the next line more stable - weights = weights / np.prod(np.power(weights, 1.0 / len(weights))) # We now have weights.prod() = 1 - lb = np.clip(x_center - weights * length / 2.0, 0.0, 1.0) - ub = np.clip(x_center + weights * length / 2.0, 0.0, 1.0) - - # Draw a Sobolev sequence in [lb, ub] - seed = np.random.randint(int(1e6)) - sobol = SobolEngine(self.dim, scramble=True, seed=seed) - pert = sobol.draw(self.n_cand).to(dtype=dtype, device=device).cpu().detach().numpy() - pert = lb + (ub - lb) * pert - - # Create a perturbation mask - prob_perturb = min(20.0 / self.dim, 1.0) - mask = np.random.rand(self.n_cand, self.dim) <= prob_perturb - ind = np.where(np.sum(mask, axis=1) == 0)[0] - mask[ind, np.random.randint(0, self.dim - 1, size=len(ind))] = 1 - - # Create candidate points - X_cand = x_center.copy() * np.ones((self.n_cand, self.dim)) - X_cand[mask] = pert[mask] - - # Figure out what device we are running on - if len(X_cand) < self.min_cuda: - device, dtype = torch.device("cpu"), torch.float64 - else: - device, dtype = self.device, self.dtype - - # We may have to move the GP to a new device - gp = gp.to(dtype=dtype, device=device) - - # We use Lanczos for sampling if we have enough data - with torch.no_grad(), gpytorch.settings.max_cholesky_size(self.max_cholesky_size): - X_cand_torch = torch.tensor(X_cand).to(device=device, dtype=dtype) - y_cand = gp.likelihood(gp(X_cand_torch)).sample(torch.Size([self.batch_size])).t().cpu().detach().numpy() - - # Remove the torch variables - del X_torch, y_torch, X_cand_torch, gp - - # De-standardize the sampled values - y_cand = mu + sigma * y_cand - - return X_cand, y_cand, hypers - - def _select_candidates(self, X_cand, y_cand): - """Select candidates.""" - X_next = np.ones((self.batch_size, self.dim)) - for i in range(self.batch_size): - # Pick the best point and make sure we never pick it again - indbest = np.argmin(y_cand[:, i]) - X_next[i, :] = deepcopy(X_cand[indbest, :]) - y_cand[indbest, :] = np.inf - return X_next - - def optimize(self): - """Run the full optimization process.""" - while self.n_evals < self.max_evals: - if len(self._fX) > 0 and self.verbose: - n_evals, fbest = self.n_evals, self._fX.min() - print(f"{n_evals}) Restarting with fbest = {fbest:.4}") - sys.stdout.flush() - - # Initialize parameters - self._restart() - - # Generate and evalute initial design points - X_init = latin_hypercube(self.n_init, self.dim) - X_init = from_unit_cube(X_init, self.lb, self.ub) - fX_init = np.array([[self.f(x)] for x in X_init]) - - # Update budget and set as initial data for this TR - self.n_evals += self.n_init - self._X = deepcopy(X_init) - self._fX = deepcopy(fX_init) - - # Append data to the global history - self.X = np.vstack((self.X, deepcopy(X_init))) - self.fX = np.vstack((self.fX, deepcopy(fX_init))) - - if self.verbose: - fbest = self._fX.min() - print(f"Starting from fbest = {fbest:.4}") - sys.stdout.flush() - - # Thompson sample to get next suggestions - - while self.n_evals < self.max_evals and self.length >= self.length_min: - # Warp inputs - X = to_unit_cube(deepcopy(self._X), self.lb, self.ub) - - # Standardize values - fX = deepcopy(self._fX).ravel() - - # Create th next batch - start = time.process_time() - X_cand, y_cand, _ = self._create_candidates( - X, fX, length=self.length, n_training_steps=self.n_training_steps, hypers={} - ) - self.mode_fit_time = time.process_time() - start - - start = time.process_time() - X_next = self._select_candidates(X_cand, y_cand) - self.acq_opt_time = time.process_time() - start - # Undo the warping - X_next = from_unit_cube(X_next, self.lb, self.ub) - # Evaluate batch - fX_next = np.array([[self.f(x)] for x in X_next]) - - # Update trust region - self._adjust_length(fX_next) - - # Update budget and append data - self.n_evals += self.batch_size - self._X = np.vstack((self._X, X_next)) - self._fX = np.vstack((self._fX, fX_next)) - - if self.verbose and fX_next.min() < self.fX.min(): - n_evals, fbest = self.n_evals, fX_next.min() - print(f"{n_evals}) New best: {fbest:.4}") - sys.stdout.flush() - - # Append data to the global history - self.X = np.vstack((self.X, deepcopy(X_next))) - self.fX = np.vstack((self.fX, deepcopy(fX_next))) - # Track time for the single iteration +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math +import sys +import time +from copy import deepcopy + +import gpytorch +import numpy as np +import torch +from torch.quasirandom import SobolEngine + +from .gp import train_gp +from .utils import from_unit_cube, latin_hypercube, to_unit_cube + +from typing import Union, Optional + +class Turbo1: + """The TuRBO-1 algorithm. + + Parameters + ---------- + f : function handle + lb : Lower variable bounds, numpy.array, shape (d,). + ub : Upper variable bounds, numpy.array, shape (d,). + n_init : Number of initial points (2*dim is recommended), int. + max_evals : Total evaluation budget, int. + batch_size : Number of points in each batch, int. + verbose : If you want to print information about the optimization progress, bool. + use_ard : If you want to use ARD for the GP kernel. + max_cholesky_size : Largest number of training points where we use Cholesky, int + n_training_steps : Number of training steps for learning the GP hypers, int + min_cuda : We use float64 on the CPU if we have this or fewer datapoints + device : Device to use for GP fitting ("cpu" or "cuda") + dtype : Dtype to use for GP fitting ("float32" or "float64") + sample_zero: switch to sample the zero vector from DoE + + Example usage: + turbo1 = Turbo1(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals) + turbo1.optimize() # Run optimization + X, fX = turbo1.X, turbo1.fX # Evaluated points + """ + + def __init__( + self, + f, + lb, + ub, + n_init, + max_evals, + batch_size=1, + verbose=True, + use_ard=True, + max_cholesky_size=2000, + n_training_steps=50, + min_cuda=1024, + device="cpu", + dtype="float64", + sample_zero:Optional[bool] = False + ): + + # Very basic input checks + assert lb.ndim == 1 and ub.ndim == 1 + assert len(lb) == len(ub) + assert np.all(ub > lb) + assert max_evals > 0 and isinstance(max_evals, int) + assert n_init > 0 and isinstance(n_init, int) + assert batch_size > 0 and isinstance(batch_size, int) + assert isinstance(verbose, bool) and isinstance(use_ard, bool) + assert max_cholesky_size >= 0 and isinstance(batch_size, int) + assert n_training_steps >= 30 and isinstance(n_training_steps, int) + assert max_evals > n_init and max_evals > batch_size + assert device == "cpu" or device == "cuda" + assert dtype == "float32" or dtype == "float64" + assert isinstance(sample_zero,bool) or sample_zero in [0,1] + if device == "cuda": + assert torch.cuda.is_available(), "can't use cuda if it's not available" + + # Save function information + self.f = f + self.dim = len(lb) + self.lb = lb + self.ub = ub + + # Settings + self.n_init = n_init + self.max_evals = max_evals + self.batch_size = batch_size + self.verbose = verbose + self.use_ard = use_ard + self.max_cholesky_size = max_cholesky_size + self.n_training_steps = n_training_steps + self.sample_zero = bool(sample_zero) + + # Hyperparameters + self.mean = np.zeros((0, 1)) + self.signal_var = np.zeros((0, 1)) + self.noise_var = np.zeros((0, 1)) + self.lengthscales = np.zeros((0, self.dim)) if self.use_ard else np.zeros((0, 1)) + + # Tolerances and counters + self.n_cand = min(100 * self.dim, 5000) + self.failtol = np.ceil(np.max([4.0 / batch_size, self.dim / batch_size])) + self.succtol = 3 + self.n_evals = 0 + + # Trust region sizes + self.length_min = 0.5 ** 7 + self.length_max = 1.6 + self.length_init = 0.8 + + # Save the full history + self.X = np.zeros((0, self.dim)) + self.fX = np.zeros((0, 1)) + + # Device and dtype for GPyTorch + self.min_cuda = min_cuda + self.dtype = torch.float32 if dtype == "float32" else torch.float64 + self.device = torch.device("cuda") if device == "cuda" else torch.device("cpu") + if self.verbose: + print("Using dtype = %s \nUsing device = %s" % (self.dtype, self.device)) + sys.stdout.flush() + + # Initialize parameters + self._restart() + + self.acq_opt_time = 0 + self.mode_fit_time = 0 + self.cum_iteration_time = 0 + + def _restart(self): + self._X = [] + self._fX = [] + self.failcount = 0 + self.succcount = 0 + self.length = self.length_init + + def _adjust_length(self, fX_next): + if np.min(fX_next) < np.min(self._fX) - 1e-3 * math.fabs(np.min(self._fX)): + self.succcount += 1 + self.failcount = 0 + else: + self.succcount = 0 + self.failcount += 1 + + if self.succcount == self.succtol: # Expand trust region + self.length = min([2.0 * self.length, self.length_max]) + self.succcount = 0 + elif self.failcount == self.failtol: # Shrink trust region + self.length /= 2.0 + self.failcount = 0 + + def _create_candidates(self, X, fX, length, n_training_steps, hypers): + """Generate candidates assuming X has been scaled to [0,1]^d.""" + # Pick the center as the point with the smallest function values + # NOTE: This may not be robust to noise, in which case the posterior mean of the GP can be used instead + assert X.min() >= 0.0 and X.max() <= 1.0 + + # Standardize function values. + mu, sigma = np.median(fX), fX.std() + sigma = 1.0 if sigma < 1e-6 else sigma + fX = (deepcopy(fX) - mu) / sigma + + # Figure out what device we are running on + if len(X) < self.min_cuda: + device, dtype = torch.device("cpu"), torch.float64 + else: + device, dtype = self.device, self.dtype + + # We use CG + Lanczos for training if we have enough data + with gpytorch.settings.max_cholesky_size(self.max_cholesky_size): + X_torch = torch.tensor(X).to(device=device, dtype=dtype) + y_torch = torch.tensor(fX).to(device=device, dtype=dtype) + gp = train_gp( + train_x=X_torch, train_y=y_torch, use_ard=self.use_ard, num_steps=n_training_steps, hypers=hypers + ) + + # Save state dict + hypers = gp.state_dict() + + # Create the trust region boundaries + x_center = X[fX.argmin().item(), :][None, :] + weights = gp.covar_module.base_kernel.lengthscale.cpu().detach().numpy().ravel() + weights = weights / weights.mean() # This will make the next line more stable + weights = weights / np.prod(np.power(weights, 1.0 / len(weights))) # We now have weights.prod() = 1 + lb = np.clip(x_center - weights * length / 2.0, 0.0, 1.0) + ub = np.clip(x_center + weights * length / 2.0, 0.0, 1.0) + + # Draw a Sobolev sequence in [lb, ub] + seed = np.random.randint(int(1e6)) + sobol = SobolEngine(self.dim, scramble=True, seed=seed) + pert = sobol.draw(self.n_cand).to(dtype=dtype, device=device).cpu().detach().numpy() + pert = lb + (ub - lb) * pert + + # Create a perturbation mask + prob_perturb = min(20.0 / self.dim, 1.0) + mask = np.random.rand(self.n_cand, self.dim) <= prob_perturb + ind = np.where(np.sum(mask, axis=1) == 0)[0] + mask[ind, np.random.randint(0, self.dim - 1, size=len(ind))] = 1 + + # Create candidate points + X_cand = x_center.copy() * np.ones((self.n_cand, self.dim)) + X_cand[mask] = pert[mask] + + # Figure out what device we are running on + if len(X_cand) < self.min_cuda: + device, dtype = torch.device("cpu"), torch.float64 + else: + device, dtype = self.device, self.dtype + + # We may have to move the GP to a new device + gp = gp.to(dtype=dtype, device=device) + + # We use Lanczos for sampling if we have enough data + with torch.no_grad(), gpytorch.settings.max_cholesky_size(self.max_cholesky_size): + X_cand_torch = torch.tensor(X_cand).to(device=device, dtype=dtype) + y_cand = gp.likelihood(gp(X_cand_torch)).sample(torch.Size([self.batch_size])).t().cpu().detach().numpy() + + # Remove the torch variables + del X_torch, y_torch, X_cand_torch, gp + + # De-standardize the sampled values + y_cand = mu + sigma * y_cand + + return X_cand, y_cand, hypers + + def _select_candidates(self, X_cand, y_cand): + """Select candidates.""" + X_next = np.ones((self.batch_size, self.dim)) + for i in range(self.batch_size): + # Pick the best point and make sure we never pick it again + indbest = np.argmin(y_cand[:, i]) + X_next[i, :] = deepcopy(X_cand[indbest, :]) + y_cand[indbest, :] = np.inf + return X_next + + def optimize(self): + """Run the full optimization process.""" + while self.n_evals < self.max_evals: + if len(self._fX) > 0 and self.verbose: + n_evals, fbest = self.n_evals, self._fX.min() + print(f"{n_evals}) Restarting with fbest = {fbest:.4}") + sys.stdout.flush() + + # Initialize parameters + self._restart() + + # Generate and evalute initial design points + X_init = latin_hypercube(self.n_init, self.dim) + + # Bypass if sample zero handle is active + if self.sample_zero: + X_init[0,:] = np.ones_like(X_init[0,:])*0.5 + + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Update budget and set as initial data for this TR + self.n_evals += self.n_init + self._X = deepcopy(X_init) + self._fX = deepcopy(fX_init) + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_init))) + self.fX = np.vstack((self.fX, deepcopy(fX_init))) + + if self.verbose: + fbest = self._fX.min() + print(f"Starting from fbest = {fbest:.4}") + sys.stdout.flush() + + # Thompson sample to get next suggestions + + while self.n_evals < self.max_evals and self.length >= self.length_min: + # Warp inputs + X = to_unit_cube(deepcopy(self._X), self.lb, self.ub) + + # Standardize values + fX = deepcopy(self._fX).ravel() + + # Create th next batch + start = time.process_time() + X_cand, y_cand, _ = self._create_candidates( + X, fX, length=self.length, n_training_steps=self.n_training_steps, hypers={} + ) + self.mode_fit_time = time.process_time() - start + + start = time.process_time() + X_next = self._select_candidates(X_cand, y_cand) + self.acq_opt_time = time.process_time() - start + # Undo the warping + X_next = from_unit_cube(X_next, self.lb, self.ub) + # Evaluate batch + fX_next = np.array([[self.f(x)] for x in X_next]) + + # Update trust region + self._adjust_length(fX_next) + + # Update budget and append data + self.n_evals += self.batch_size + self._X = np.vstack((self._X, X_next)) + self._fX = np.vstack((self._fX, fX_next)) + + if self.verbose and fX_next.min() < self.fX.min(): + n_evals, fbest = self.n_evals, fX_next.min() + print(f"{n_evals}) New best: {fbest:.4}") + sys.stdout.flush() + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_next))) + self.fX = np.vstack((self.fX, deepcopy(fX_next))) + # Track time for the single iteration self.cum_iteration_time = time.process_time() \ No newline at end of file diff --git a/mylib/lib_turbo1/turbo/turbo_m.py b/mylib/lib_turbo1/turbo/turbo_m.py index 6e70b36..f1581bd 100644 --- a/mylib/lib_turbo1/turbo/turbo_m.py +++ b/mylib/lib_turbo1/turbo/turbo_m.py @@ -1,260 +1,272 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import math -import sys -import time -from copy import deepcopy - -import gpytorch -import numpy as np -import torch - -from .gp import train_gp -from .turbo_1 import Turbo1 -from .utils import from_unit_cube, latin_hypercube, to_unit_cube - - -class TurboM(Turbo1): - """The TuRBO-m algorithm. - - Parameters - ---------- - f : function handle - lb : Lower variable bounds, numpy.array, shape (d,). - ub : Upper variable bounds, numpy.array, shape (d,). - n_init : Number of initial points *FOR EACH TRUST REGION* (2*dim is recommended), int. - max_evals : Total evaluation budget, int. - n_trust_regions : Number of trust regions - batch_size : Number of points in each batch, int. - verbose : If you want to print information about the optimization progress, bool. - use_ard : If you want to use ARD for the GP kernel. - max_cholesky_size : Largest number of training points where we use Cholesky, int - n_training_steps : Number of training steps for learning the GP hypers, int - min_cuda : We use float64 on the CPU if we have this or fewer datapoints - device : Device to use for GP fitting ("cpu" or "cuda") - dtype : Dtype to use for GP fitting ("float32" or "float64") - - Example usage: - turbo5 = TurboM(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals, n_trust_regions=5) - turbo5.optimize() # Run optimization - X, fX = turbo5.X, turbo5.fX # Evaluated points - """ - - def __init__( - self, - f, - lb, - ub, - n_init, - max_evals, - n_trust_regions, - batch_size=1, - verbose=True, - use_ard=True, - max_cholesky_size=2000, - n_training_steps=50, - min_cuda=1024, - device="cpu", - dtype="float64", - ): - self.n_trust_regions = n_trust_regions - self.acq_opt_time = 0 - self.mode_fit_time = 0 - self.cum_iteration_time = 0 - super().__init__( - f=f, - lb=lb, - ub=ub, - n_init=n_init, - max_evals=max_evals, - batch_size=batch_size, - verbose=verbose, - use_ard=use_ard, - max_cholesky_size=max_cholesky_size, - n_training_steps=n_training_steps, - min_cuda=min_cuda, - device=device, - dtype=dtype, - ) - - self.succtol = 3 - self.failtol = max(5, self.dim) - - # Very basic input checks - assert n_trust_regions > 1 and isinstance(max_evals, int) - assert max_evals > n_trust_regions * n_init, "Not enough trust regions to do initial evaluations" - assert max_evals > batch_size, "Not enough evaluations to do a single batch" - - # Remember the hypers for trust regions we don't sample from - self.hypers = [{} for _ in range(self.n_trust_regions)] - - # Initialize parameters - self._restart() - - def _restart(self): - self._idx = np.zeros((0, 1), dtype=int) # Track what trust region proposed what using an index vector - self.failcount = np.zeros(self.n_trust_regions, dtype=int) - self.succcount = np.zeros(self.n_trust_regions, dtype=int) - self.length = self.length_init * np.ones(self.n_trust_regions) - - def _adjust_length(self, fX_next, i): - assert i >= 0 and i <= self.n_trust_regions - 1 - - fX_min = self.fX[self._idx[:, 0] == i, 0].min() # Target value - if fX_next.min() < fX_min - 1e-3 * math.fabs(fX_min): - self.succcount[i] += 1 - self.failcount[i] = 0 - else: - self.succcount[i] = 0 - self.failcount[i] += len(fX_next) # NOTE: Add size of the batch for this TR - - if self.succcount[i] == self.succtol: # Expand trust region - self.length[i] = min([2.0 * self.length[i], self.length_max]) - self.succcount[i] = 0 - elif self.failcount[i] >= self.failtol: # Shrink trust region (we may have exceeded the failtol) - self.length[i] /= 2.0 - self.failcount[i] = 0 - - def _select_candidates(self, X_cand, y_cand): - """Select candidates from samples from all trust regions.""" - assert X_cand.shape == (self.n_trust_regions, self.n_cand, self.dim) - assert y_cand.shape == (self.n_trust_regions, self.n_cand, self.batch_size) - assert X_cand.min() >= 0.0 and X_cand.max() <= 1.0 and np.all(np.isfinite(y_cand)) - - X_next = np.zeros((self.batch_size, self.dim)) - idx_next = np.zeros((self.batch_size, 1), dtype=int) - for k in range(self.batch_size): - i, j = np.unravel_index(np.argmin(y_cand[:, :, k]), (self.n_trust_regions, self.n_cand)) - assert y_cand[:, :, k].min() == y_cand[i, j, k] - X_next[k, :] = deepcopy(X_cand[i, j, :]) - idx_next[k, 0] = i - assert np.isfinite(y_cand[i, j, k]) # Just to make sure we never select nan or inf - - # Make sure we never pick this point again - y_cand[i, j, :] = np.inf - - return X_next, idx_next - - def optimize(self): - """Run the full optimization process.""" - # Create initial points for each TR - for i in range(self.n_trust_regions): - X_init = latin_hypercube(self.n_init, self.dim) - X_init = from_unit_cube(X_init, self.lb, self.ub) - fX_init = np.array([[self.f(x)] for x in X_init]) - - # Update budget and set as initial data for this TR - self.X = np.vstack((self.X, X_init)) - self.fX = np.vstack((self.fX, fX_init)) - self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) - self.n_evals += self.n_init - - if self.verbose: - fbest = fX_init.min() - print(f"TR-{i} starting from: {fbest:.4}") - sys.stdout.flush() - - # Thompson sample to get next suggestions - while self.n_evals < self.max_evals: - - # Generate candidates from each TR - X_cand = np.zeros((self.n_trust_regions, self.n_cand, self.dim)) - y_cand = np.inf * np.ones((self.n_trust_regions, self.n_cand, self.batch_size)) - start = time.process_time() - for i in range(self.n_trust_regions): - idx = np.where(self._idx == i)[0] # Extract all "active" indices - - # Get the points, values the active values - X = deepcopy(self.X[idx, :]) - X = to_unit_cube(X, self.lb, self.ub) - - # Get the values from the standardized data - fX = deepcopy(self.fX[idx, 0].ravel()) - - # Don't retrain the model if the training data hasn't changed - n_training_steps = 0 if self.hypers[i] else self.n_training_steps - - # Create new candidates - - X_cand[i, :, :], y_cand[i, :, :], self.hypers[i] = self._create_candidates( - X, fX, length=self.length[i], n_training_steps=n_training_steps, hypers=self.hypers[i] - ) - self.mode_fit_time = time.process_time() - start - # Select the next candidates - start = time.process_time() - X_next, idx_next = self._select_candidates(X_cand, y_cand) - assert X_next.min() >= 0.0 and X_next.max() <= 1.0 - self.acq_opt_time = time.process_time() - start - - # Undo the warping - X_next = from_unit_cube(X_next, self.lb, self.ub) - - # Evaluate batch - fX_next = np.array([[self.f(x)] for x in X_next]) - - # Update trust regions - for i in range(self.n_trust_regions): - idx_i = np.where(idx_next == i)[0] - if len(idx_i) > 0: - self.hypers[i] = {} # Remove model hypers - fX_i = fX_next[idx_i] - - if self.verbose and fX_i.min() < self.fX.min() - 1e-3 * math.fabs(self.fX.min()): - n_evals, fbest = self.n_evals, fX_i.min() - print(f"{n_evals}) New best @ TR-{i}: {fbest:.4}") - sys.stdout.flush() - self._adjust_length(fX_i, i) - - # Update budget and append data - self.n_evals += self.batch_size - self.X = np.vstack((self.X, deepcopy(X_next))) - self.fX = np.vstack((self.fX, deepcopy(fX_next))) - self._idx = np.vstack((self._idx, deepcopy(idx_next))) - - # Check if any TR needs to be restarted - for i in range(self.n_trust_regions): - if self.length[i] < self.length_min: # Restart trust region if converged - idx_i = self._idx[:, 0] == i - - if self.verbose: - n_evals, fbest = self.n_evals, self.fX[idx_i, 0].min() - print(f"{n_evals}) TR-{i} converged to: : {fbest:.4}") - sys.stdout.flush() - - # Reset length and counters, remove old data from trust region - self.length[i] = self.length_init - self.succcount[i] = 0 - self.failcount[i] = 0 - self._idx[idx_i, 0] = -1 # Remove points from trust region - self.hypers[i] = {} # Remove model hypers - - # Create a new initial design - X_init = latin_hypercube(self.n_init, self.dim) - X_init = from_unit_cube(X_init, self.lb, self.ub) - fX_init = np.array([[self.f(x)] for x in X_init]) - - # Print progress - if self.verbose: - n_evals, fbest = self.n_evals, fX_init.min() - print(f"{n_evals}) TR-{i} is restarting from: : {fbest:.4}") - sys.stdout.flush() - - # Append data to local history - self.X = np.vstack((self.X, X_init)) - self.fX = np.vstack((self.fX, fX_init)) - self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) - self.n_evals += self.n_init - - # Track time for the single iteration - self.cum_iteration_time = time.process_time() - - +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math +import sys +import time +from copy import deepcopy + +import gpytorch +import numpy as np +import torch + +from .gp import train_gp +from .turbo_1 import Turbo1 +from .utils import from_unit_cube, latin_hypercube, to_unit_cube + + +class TurboM(Turbo1): + """The TuRBO-m algorithm. + + Parameters + ---------- + f : function handle + lb : Lower variable bounds, numpy.array, shape (d,). + ub : Upper variable bounds, numpy.array, shape (d,). + n_init : Number of initial points *FOR EACH TRUST REGION* (2*dim is recommended), int. + max_evals : Total evaluation budget, int. + n_trust_regions : Number of trust regions + batch_size : Number of points in each batch, int. + verbose : If you want to print information about the optimization progress, bool. + use_ard : If you want to use ARD for the GP kernel. + max_cholesky_size : Largest number of training points where we use Cholesky, int + n_training_steps : Number of training steps for learning the GP hypers, int + min_cuda : We use float64 on the CPU if we have this or fewer datapoints + device : Device to use for GP fitting ("cpu" or "cuda") + dtype : Dtype to use for GP fitting ("float32" or "float64") + sample_zero: switch to sample the zero vector from DoE + + Example usage: + turbo5 = TurboM(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals, n_trust_regions=5) + turbo5.optimize() # Run optimization + X, fX = turbo5.X, turbo5.fX # Evaluated points + """ + + def __init__( + self, + f, + lb, + ub, + n_init, + max_evals, + n_trust_regions, + batch_size=1, + verbose=True, + use_ard=True, + max_cholesky_size=2000, + n_training_steps=50, + min_cuda=1024, + device="cpu", + dtype="float64", + sample_zero=False + ): + self.n_trust_regions = n_trust_regions + self.acq_opt_time = 0 + self.mode_fit_time = 0 + self.cum_iteration_time = 0 + super().__init__( + f=f, + lb=lb, + ub=ub, + n_init=n_init, + max_evals=max_evals, + batch_size=batch_size, + verbose=verbose, + use_ard=use_ard, + max_cholesky_size=max_cholesky_size, + n_training_steps=n_training_steps, + min_cuda=min_cuda, + device=device, + dtype=dtype, + sample_zero=sample_zero + ) + + self.succtol = 3 + self.failtol = max(5, self.dim) + + # Very basic input checks + assert n_trust_regions > 1 and isinstance(max_evals, int) + assert max_evals > n_trust_regions * n_init, "Not enough trust regions to do initial evaluations" + assert max_evals > batch_size, "Not enough evaluations to do a single batch" + + # Remember the hypers for trust regions we don't sample from + self.hypers = [{} for _ in range(self.n_trust_regions)] + + # Initialize parameters + self._restart() + + def _restart(self): + self._idx = np.zeros((0, 1), dtype=int) # Track what trust region proposed what using an index vector + self.failcount = np.zeros(self.n_trust_regions, dtype=int) + self.succcount = np.zeros(self.n_trust_regions, dtype=int) + self.length = self.length_init * np.ones(self.n_trust_regions) + + def _adjust_length(self, fX_next, i): + assert i >= 0 and i <= self.n_trust_regions - 1 + + fX_min = self.fX[self._idx[:, 0] == i, 0].min() # Target value + if fX_next.min() < fX_min - 1e-3 * math.fabs(fX_min): + self.succcount[i] += 1 + self.failcount[i] = 0 + else: + self.succcount[i] = 0 + self.failcount[i] += len(fX_next) # NOTE: Add size of the batch for this TR + + if self.succcount[i] == self.succtol: # Expand trust region + self.length[i] = min([2.0 * self.length[i], self.length_max]) + self.succcount[i] = 0 + elif self.failcount[i] >= self.failtol: # Shrink trust region (we may have exceeded the failtol) + self.length[i] /= 2.0 + self.failcount[i] = 0 + + def _select_candidates(self, X_cand, y_cand): + """Select candidates from samples from all trust regions.""" + assert X_cand.shape == (self.n_trust_regions, self.n_cand, self.dim) + assert y_cand.shape == (self.n_trust_regions, self.n_cand, self.batch_size) + assert X_cand.min() >= 0.0 and X_cand.max() <= 1.0 and np.all(np.isfinite(y_cand)) + + X_next = np.zeros((self.batch_size, self.dim)) + idx_next = np.zeros((self.batch_size, 1), dtype=int) + for k in range(self.batch_size): + i, j = np.unravel_index(np.argmin(y_cand[:, :, k]), (self.n_trust_regions, self.n_cand)) + assert y_cand[:, :, k].min() == y_cand[i, j, k] + X_next[k, :] = deepcopy(X_cand[i, j, :]) + idx_next[k, 0] = i + assert np.isfinite(y_cand[i, j, k]) # Just to make sure we never select nan or inf + + # Make sure we never pick this point again + y_cand[i, j, :] = np.inf + + return X_next, idx_next + + def optimize(self): + """Run the full optimization process.""" + # Create initial points for each TR + for i in range(self.n_trust_regions): + X_init = latin_hypercube(self.n_init, self.dim) + + # Sample the zero vector as part of the DoE + if self.sample_zero: + X_init[0,:] = np.ones_like(X_init[0,:])*0.5 + + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Update budget and set as initial data for this TR + self.X = np.vstack((self.X, X_init)) + self.fX = np.vstack((self.fX, fX_init)) + self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) + self.n_evals += self.n_init + + if self.verbose: + fbest = fX_init.min() + print(f"TR-{i} starting from: {fbest:.4}") + sys.stdout.flush() + + # Thompson sample to get next suggestions + while self.n_evals < self.max_evals: + + # Generate candidates from each TR + X_cand = np.zeros((self.n_trust_regions, self.n_cand, self.dim)) + y_cand = np.inf * np.ones((self.n_trust_regions, self.n_cand, self.batch_size)) + start = time.process_time() + for i in range(self.n_trust_regions): + idx = np.where(self._idx == i)[0] # Extract all "active" indices + + # Get the points, values the active values + X = deepcopy(self.X[idx, :]) + X = to_unit_cube(X, self.lb, self.ub) + + # Get the values from the standardized data + fX = deepcopy(self.fX[idx, 0].ravel()) + + # Don't retrain the model if the training data hasn't changed + n_training_steps = 0 if self.hypers[i] else self.n_training_steps + + # Create new candidates + + X_cand[i, :, :], y_cand[i, :, :], self.hypers[i] = self._create_candidates( + X, fX, length=self.length[i], n_training_steps=n_training_steps, hypers=self.hypers[i] + ) + self.mode_fit_time = time.process_time() - start + # Select the next candidates + start = time.process_time() + X_next, idx_next = self._select_candidates(X_cand, y_cand) + assert X_next.min() >= 0.0 and X_next.max() <= 1.0 + self.acq_opt_time = time.process_time() - start + + # Undo the warping + X_next = from_unit_cube(X_next, self.lb, self.ub) + + # Evaluate batch + fX_next = np.array([[self.f(x)] for x in X_next]) + + # Update trust regions + for i in range(self.n_trust_regions): + idx_i = np.where(idx_next == i)[0] + if len(idx_i) > 0: + self.hypers[i] = {} # Remove model hypers + fX_i = fX_next[idx_i] + + if self.verbose and fX_i.min() < self.fX.min() - 1e-3 * math.fabs(self.fX.min()): + n_evals, fbest = self.n_evals, fX_i.min() + print(f"{n_evals}) New best @ TR-{i}: {fbest:.4}") + sys.stdout.flush() + self._adjust_length(fX_i, i) + + # Update budget and append data + self.n_evals += self.batch_size + self.X = np.vstack((self.X, deepcopy(X_next))) + self.fX = np.vstack((self.fX, deepcopy(fX_next))) + self._idx = np.vstack((self._idx, deepcopy(idx_next))) + + # Check if any TR needs to be restarted + for i in range(self.n_trust_regions): + if self.length[i] < self.length_min: # Restart trust region if converged + idx_i = self._idx[:, 0] == i + + if self.verbose: + n_evals, fbest = self.n_evals, self.fX[idx_i, 0].min() + print(f"{n_evals}) TR-{i} converged to: : {fbest:.4}") + sys.stdout.flush() + + # Reset length and counters, remove old data from trust region + self.length[i] = self.length_init + self.succcount[i] = 0 + self.failcount[i] = 0 + self._idx[idx_i, 0] = -1 # Remove points from trust region + self.hypers[i] = {} # Remove model hypers + + # Create a new initial design + X_init = latin_hypercube(self.n_init, self.dim) + + if self.sample_zero: + X_init[0,:] = np.ones_like(X_init[0,:])*0.5 + + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Print progress + if self.verbose: + n_evals, fbest = self.n_evals, fX_init.min() + print(f"{n_evals}) TR-{i} is restarting from: : {fbest:.4}") + sys.stdout.flush() + + # Append data to local history + self.X = np.vstack((self.X, X_init)) + self.fX = np.vstack((self.fX, fX_init)) + self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) + self.n_evals += self.n_init + + # Track time for the single iteration + self.cum_iteration_time = time.process_time() + + diff --git a/mylib/lib_turbo1/turbo/utils.py b/mylib/lib_turbo1/turbo/utils.py index 0806812..d9b8837 100644 --- a/mylib/lib_turbo1/turbo/utils.py +++ b/mylib/lib_turbo1/turbo/utils.py @@ -1,39 +1,39 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import numpy as np - - -def to_unit_cube(x, lb, ub): - """Project to [0, 1]^d from hypercube with bounds lb and ub""" - assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 - xx = (x - lb) / (ub - lb) - return xx - - -def from_unit_cube(x, lb, ub): - """Project from [0, 1]^d to hypercube with bounds lb and ub""" - assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 - xx = x * (ub - lb) + lb - return xx - - -def latin_hypercube(n_pts, dim): - """Basic Latin hypercube implementation with center perturbation.""" - X = np.zeros((n_pts, dim)) - centers = (1.0 + 2.0 * np.arange(0.0, n_pts)) / float(2 * n_pts) - for i in range(dim): # Shuffle the center locataions for each dimension. - X[:, i] = centers[np.random.permutation(n_pts)] - - # Add some perturbations within each box - pert = np.random.uniform(-1.0, 1.0, (n_pts, dim)) / float(2 * n_pts) - X += pert - return X +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import numpy as np + + +def to_unit_cube(x, lb, ub): + """Project to [0, 1]^d from hypercube with bounds lb and ub""" + assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 + xx = (x - lb) / (ub - lb) + return xx + + +def from_unit_cube(x, lb, ub): + """Project from [0, 1]^d to hypercube with bounds lb and ub""" + assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 + xx = x * (ub - lb) + lb + return xx + + +def latin_hypercube(n_pts, dim): + """Basic Latin hypercube implementation with center perturbation.""" + X = np.zeros((n_pts, dim)) + centers = (1.0 + 2.0 * np.arange(0.0, n_pts)) / float(2 * n_pts) + for i in range(dim): # Shuffle the center locataions for each dimension. + X[:, i] = centers[np.random.permutation(n_pts)] + + # Add some perturbations within each box + pert = np.random.uniform(-1.0, 1.0, (n_pts, dim)) / float(2 * n_pts) + X += pert + return X diff --git a/mylib/lib_turbom/CONTRIBUTORS.md b/mylib/lib_turbom/CONTRIBUTORS.md index 4fd0bc8..149abe2 100644 --- a/mylib/lib_turbom/CONTRIBUTORS.md +++ b/mylib/lib_turbom/CONTRIBUTORS.md @@ -1,2 +1,2 @@ -Code written by: -- David Eriksson +Code written by: +- David Eriksson diff --git a/mylib/lib_turbom/LICENSE.md b/mylib/lib_turbom/LICENSE.md index 488547e..063c3cb 100644 --- a/mylib/lib_turbom/LICENSE.md +++ b/mylib/lib_turbom/LICENSE.md @@ -1,41 +1,41 @@ -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by the text below. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under this License. - -This License governs use of the accompanying Work, and your use of the Work constitutes acceptance of this License. - -You may use this Work for any non-commercial purpose, subject to the restrictions in this License. Some purposes which can be non-commercial are teaching, academic research, and personal experimentation. You may also distribute this Work with books or other teaching materials, or publish the Work on websites, that are intended to teach the use of the Work. - -You may not use or distribute this Work, or any derivative works, outputs, or results from the Work, in any form for commercial purposes. Non-exhaustive examples of commercial purposes would be running business operations, licensing, leasing, or selling the Work, or distributing the Work for use with commercial products. - -You may modify this Work and distribute the modified Work for non-commercial purposes, however, you may not grant rights to the Work or derivative works that are broader than or in conflict with those provided by this License. For example, you may not distribute modifications of the Work under terms that would permit commercial use, or under terms that purport to require the Work or derivative works to be sublicensed to others. - -In return, we require that you agree: - -1. Not to remove any copyright or other notices from the Work. - -2. That if you distribute the Work in Source or Object form, you will include a verbatim copy of this License. - -3. That if you distribute derivative works of the Work in Source form, you do so only under a license that includes all of the provisions of this License and is not in conflict with this License, and if you distribute derivative works of the Work solely in Object form you do so only under a license that complies with this License. - -4. That if you have modified the Work or created derivative works from the Work, and distribute such modifications or derivative works, you will cause the modified files to carry prominent notices so that recipients know that they are not receiving the original Work. Such notices must state: (i) that you have changed the Work; and (ii) the date of any changes. - -5. If you publicly use the Work or any output or result of the Work, you will provide a notice with such use that provides any person who uses, views, accesses, interacts with, or is otherwise exposed to the Work (i) with information of the nature of the Work, (ii) with a link to the Work, and (iii) a notice that the Work is available under this License. - -6. THAT THE WORK COMES "AS IS", WITH NO WARRANTIES. THIS MEANS NO EXPRESS, IMPLIED OR STATUTORY WARRANTY, INCLUDING WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT. ALSO, YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. - -7. THAT NEITHER UBER TECHNOLOGIES, INC. NOR ANY OF ITS AFFILIATES, SUPPLIERS, SUCCESSORS, NOR ASSIGNS WILL BE LIABLE FOR ANY DAMAGES RELATED TO THE WORK OR THIS LICENSE, INCLUDING DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, TO THE MAXIMUM EXTENT THE LAW PERMITS, NO MATTER WHAT LEGAL THEORY IT IS BASED ON. ALSO, YOU MUST PASS THIS LIMITATION OF LIABILITY ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. - -8. That if you sue anyone over patents that you think may apply to the Work or anyone's use of the Work, your license to the Work ends automatically. - -9. That your rights under the License end automatically if you breach it in any way. - -10. Uber Technologies, Inc. reserves all rights not expressly granted to you in this License. +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by the text below. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under this License. + +This License governs use of the accompanying Work, and your use of the Work constitutes acceptance of this License. + +You may use this Work for any non-commercial purpose, subject to the restrictions in this License. Some purposes which can be non-commercial are teaching, academic research, and personal experimentation. You may also distribute this Work with books or other teaching materials, or publish the Work on websites, that are intended to teach the use of the Work. + +You may not use or distribute this Work, or any derivative works, outputs, or results from the Work, in any form for commercial purposes. Non-exhaustive examples of commercial purposes would be running business operations, licensing, leasing, or selling the Work, or distributing the Work for use with commercial products. + +You may modify this Work and distribute the modified Work for non-commercial purposes, however, you may not grant rights to the Work or derivative works that are broader than or in conflict with those provided by this License. For example, you may not distribute modifications of the Work under terms that would permit commercial use, or under terms that purport to require the Work or derivative works to be sublicensed to others. + +In return, we require that you agree: + +1. Not to remove any copyright or other notices from the Work. + +2. That if you distribute the Work in Source or Object form, you will include a verbatim copy of this License. + +3. That if you distribute derivative works of the Work in Source form, you do so only under a license that includes all of the provisions of this License and is not in conflict with this License, and if you distribute derivative works of the Work solely in Object form you do so only under a license that complies with this License. + +4. That if you have modified the Work or created derivative works from the Work, and distribute such modifications or derivative works, you will cause the modified files to carry prominent notices so that recipients know that they are not receiving the original Work. Such notices must state: (i) that you have changed the Work; and (ii) the date of any changes. + +5. If you publicly use the Work or any output or result of the Work, you will provide a notice with such use that provides any person who uses, views, accesses, interacts with, or is otherwise exposed to the Work (i) with information of the nature of the Work, (ii) with a link to the Work, and (iii) a notice that the Work is available under this License. + +6. THAT THE WORK COMES "AS IS", WITH NO WARRANTIES. THIS MEANS NO EXPRESS, IMPLIED OR STATUTORY WARRANTY, INCLUDING WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT. ALSO, YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +7. THAT NEITHER UBER TECHNOLOGIES, INC. NOR ANY OF ITS AFFILIATES, SUPPLIERS, SUCCESSORS, NOR ASSIGNS WILL BE LIABLE FOR ANY DAMAGES RELATED TO THE WORK OR THIS LICENSE, INCLUDING DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, TO THE MAXIMUM EXTENT THE LAW PERMITS, NO MATTER WHAT LEGAL THEORY IT IS BASED ON. ALSO, YOU MUST PASS THIS LIMITATION OF LIABILITY ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +8. That if you sue anyone over patents that you think may apply to the Work or anyone's use of the Work, your license to the Work ends automatically. + +9. That your rights under the License end automatically if you breach it in any way. + +10. Uber Technologies, Inc. reserves all rights not expressly granted to you in this License. diff --git a/mylib/lib_turbom/README.md b/mylib/lib_turbom/README.md index a49c27a..b0c3c98 100644 --- a/mylib/lib_turbom/README.md +++ b/mylib/lib_turbom/README.md @@ -1,95 +1,95 @@ -## Overview - -This is the code-release for the TuRBO algorithm from ***Scalable Global Optimization via Local Bayesian Optimization*** appearing in NeurIPS 2019. This is an implementation for the noise-free case and may not work well if observations are noisy as the center of the trust region should be chosen based on the posterior mean in this case. - -Note that TuRBO is a **minimization** algorithm, so please make sure you reformulate potential maximization problems. - -## Benchmark functions - -### Robot pushing -The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We have made the following changes to the code when running our experiments: - -1. We turned off the visualization, which speeds up the function evaluations. -2. We replaced all instances of ```np.random.normal(0, 0.01)``` by ```np.random.normal(0, 1e-6)``` in ```push_utils.py```. This makes the function close to noise-free. Another option is to average over several evaluations using the original code -3. We flipped the sign of the objective function to turn this into a minimization problem. - -Dependencies: ```numpy ```, ```pygame```, ```box2d-py``` - -### Rover -The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We used the large version of the problem, which has 60 dimensions. We have flipped the sign of the objective function to turn this into a minimization problem. - -Dependencies: ```numpy```, ```scipy``` - -### Lunar - -The lunar code is available in the OpenAI gym: https://github.com/openai/gym. The goal of the problem is to learn the parameter values of a controller for the lunar lander. The controller we learn is a modification of the original heuristic controller which takes the form: - -``` -def heuristic_Controller(s, w): - angle_targ = s[0] * w[0] + s[2] * w[1] - if angle_targ > w[2]: - angle_targ = w[2] - if angle_targ < -w[2]: - angle_targ = -w[2] - hover_targ = w[3] * np.abs(s[0]) - - angle_todo = (angle_targ - s[4]) * w[4] - (s[5]) * w[5] - hover_todo = (hover_targ - s[1]) * w[6] - (s[3]) * w[7] - - if s[6] or s[7]: - angle_todo = w[8] - hover_todo = -(s[3]) * w[9] - - a = 0 - if hover_todo > np.abs(angle_todo) and hover_todo > w[10]: - a = 2 - elif angle_todo < -w[11]: - a = 3 - elif angle_todo > +w[11]: - a = 1 - return a -``` - -We use the constraints 0 <= w_i <= 2 for all parameters. We use ```INITIAL_RANDOM = 1500.0``` to make the problem more challenging. - -For more information about the logic behind this controller and how to integrate it with ```gym```, take a look at the original heuristic controller source code: https://github.com/openai/gym/blob/master/gym/envs/box2d/lunar_lander.py#L364 - -Dependencies: ```gym```, ```box2d-py``` - -### Cosmological constant -The code for the cosmological constant problem is available here: https://ascl.net/1306.012. You need to follow the instructions and compile the FORTRAN code. This gives you an executable ```CAMB``` that you can call to run the simulation. - -The parameter names and bounds that we tune are the following: - -``` -ombh2: [0.01, 0.25] -omch2: [0.01, 0.25] -omnuh2: [0.01, 0.25] -omk: [0.01, 0.25] -hubble: [52.5, 100] -temp_cmb: [2.7, 2.8] -hefrac: [0.2, 0.3] -mneu: [2.9, 3.09] -scalar_amp: [1.5e-9, 2.6e-8] -scalar_spec_ind: [0.72, 5] -rf_fudge: [0, 100] -rf_fudge_he: [0, 100] -``` - -## Examples -Check the examples folder for two examples on how to use Turbo-1 and Turbo-n. - -## Citing us - -The final version of the paper is available at: http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization. - -``` -@inproceedings{eriksson2019scalable, - title = {Scalable Global Optimization via Local {Bayesian} Optimization}, - author = {Eriksson, David and Pearce, Michael and Gardner, Jacob and Turner, Ryan D and Poloczek, Matthias}, - booktitle = {Advances in Neural Information Processing Systems}, - pages = {5496--5507}, - year = {2019}, - url = {http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization.pdf}, -} -``` +## Overview + +This is the code-release for the TuRBO algorithm from ***Scalable Global Optimization via Local Bayesian Optimization*** appearing in NeurIPS 2019. This is an implementation for the noise-free case and may not work well if observations are noisy as the center of the trust region should be chosen based on the posterior mean in this case. + +Note that TuRBO is a **minimization** algorithm, so please make sure you reformulate potential maximization problems. + +## Benchmark functions + +### Robot pushing +The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We have made the following changes to the code when running our experiments: + +1. We turned off the visualization, which speeds up the function evaluations. +2. We replaced all instances of ```np.random.normal(0, 0.01)``` by ```np.random.normal(0, 1e-6)``` in ```push_utils.py```. This makes the function close to noise-free. Another option is to average over several evaluations using the original code +3. We flipped the sign of the objective function to turn this into a minimization problem. + +Dependencies: ```numpy ```, ```pygame```, ```box2d-py``` + +### Rover +The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We used the large version of the problem, which has 60 dimensions. We have flipped the sign of the objective function to turn this into a minimization problem. + +Dependencies: ```numpy```, ```scipy``` + +### Lunar + +The lunar code is available in the OpenAI gym: https://github.com/openai/gym. The goal of the problem is to learn the parameter values of a controller for the lunar lander. The controller we learn is a modification of the original heuristic controller which takes the form: + +``` +def heuristic_Controller(s, w): + angle_targ = s[0] * w[0] + s[2] * w[1] + if angle_targ > w[2]: + angle_targ = w[2] + if angle_targ < -w[2]: + angle_targ = -w[2] + hover_targ = w[3] * np.abs(s[0]) + + angle_todo = (angle_targ - s[4]) * w[4] - (s[5]) * w[5] + hover_todo = (hover_targ - s[1]) * w[6] - (s[3]) * w[7] + + if s[6] or s[7]: + angle_todo = w[8] + hover_todo = -(s[3]) * w[9] + + a = 0 + if hover_todo > np.abs(angle_todo) and hover_todo > w[10]: + a = 2 + elif angle_todo < -w[11]: + a = 3 + elif angle_todo > +w[11]: + a = 1 + return a +``` + +We use the constraints 0 <= w_i <= 2 for all parameters. We use ```INITIAL_RANDOM = 1500.0``` to make the problem more challenging. + +For more information about the logic behind this controller and how to integrate it with ```gym```, take a look at the original heuristic controller source code: https://github.com/openai/gym/blob/master/gym/envs/box2d/lunar_lander.py#L364 + +Dependencies: ```gym```, ```box2d-py``` + +### Cosmological constant +The code for the cosmological constant problem is available here: https://ascl.net/1306.012. You need to follow the instructions and compile the FORTRAN code. This gives you an executable ```CAMB``` that you can call to run the simulation. + +The parameter names and bounds that we tune are the following: + +``` +ombh2: [0.01, 0.25] +omch2: [0.01, 0.25] +omnuh2: [0.01, 0.25] +omk: [0.01, 0.25] +hubble: [52.5, 100] +temp_cmb: [2.7, 2.8] +hefrac: [0.2, 0.3] +mneu: [2.9, 3.09] +scalar_amp: [1.5e-9, 2.6e-8] +scalar_spec_ind: [0.72, 5] +rf_fudge: [0, 100] +rf_fudge_he: [0, 100] +``` + +## Examples +Check the examples folder for two examples on how to use Turbo-1 and Turbo-n. + +## Citing us + +The final version of the paper is available at: http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization. + +``` +@inproceedings{eriksson2019scalable, + title = {Scalable Global Optimization via Local {Bayesian} Optimization}, + author = {Eriksson, David and Pearce, Michael and Gardner, Jacob and Turner, Ryan D and Poloczek, Matthias}, + booktitle = {Advances in Neural Information Processing Systems}, + pages = {5496--5507}, + year = {2019}, + url = {http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization.pdf}, +} +``` diff --git a/mylib/lib_turbom/examples/Turbo1.ipynb b/mylib/lib_turbom/examples/Turbo1.ipynb index 7226a00..23004bd 100644 --- a/mylib/lib_turbom/examples/Turbo1.ipynb +++ b/mylib/lib_turbom/examples/Turbo1.ipynb @@ -1,254 +1,254 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simple example of TuRBO-1" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from turbo import Turbo1\n", - "import numpy as np\n", - "import torch\n", - "import math\n", - "import matplotlib\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up an optimization problem class" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "class Levy:\n", - " def __init__(self, dim=10):\n", - " self.dim = dim\n", - " self.lb = -5 * np.ones(dim)\n", - " self.ub = 10 * np.ones(dim)\n", - " \n", - " def __call__(self, x):\n", - " assert len(x) == self.dim\n", - " assert x.ndim == 1\n", - " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", - " w = 1 + (x - 1.0) / 4.0\n", - " val = np.sin(np.pi * w[0]) ** 2 + \\\n", - " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", - " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", - " return val\n", - "\n", - "f = Levy(10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a Turbo optimizer instance" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using dtype = torch.float64 \n", - "Using device = cpu\n" - ] - } - ], - "source": [ - "turbo1 = Turbo1(\n", - " f=f, # Handle to objective function\n", - " lb=f.lb, # Numpy array specifying lower bounds\n", - " ub=f.ub, # Numpy array specifying upper bounds\n", - " n_init=20, # Number of initial bounds from an Latin hypercube design\n", - " max_evals = 1000, # Maximum number of evaluations\n", - " batch_size=10, # How large batch size TuRBO uses\n", - " verbose=True, # Print information from each batch\n", - " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", - " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", - " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", - " min_cuda=1024, # Run on the CPU for small datasets\n", - " device=\"cpu\", # \"cpu\" or \"cuda\"\n", - " dtype=\"float64\", # float64 or float32\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Run the optimization process" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starting from fbest = 20.98\n", - "50) New best: 15.65\n", - "80) New best: 11.27\n", - "90) New best: 9.325\n", - "100) New best: 8.288\n", - "110) New best: 6.944\n", - "120) New best: 5.974\n", - "140) New best: 5.951\n", - "160) New best: 5.905\n", - "170) New best: 5.905\n", - "180) New best: 5.822\n", - "190) New best: 5.785\n", - "200) New best: 5.759\n", - "220) New best: 5.738\n", - "230) New best: 5.683\n", - "240) Restarting with fbest = 5.683\n", - "Starting from fbest = 32.5\n", - "320) New best: 5.526\n", - "330) New best: 3.95\n", - "350) New best: 1.736\n", - "370) New best: 1.229\n", - "410) New best: 1.206\n", - "420) New best: 1.193\n", - "430) New best: 1.191\n", - "440) New best: 1.163\n", - "450) New best: 1.145\n", - "460) New best: 1.06\n", - "480) New best: 1.024\n", - "490) New best: 1.01\n", - "500) New best: 1.001\n", - "530) Restarting with fbest = 1.001\n", - "Starting from fbest = 12.85\n", - "730) Restarting with fbest = 8.634\n", - "Starting from fbest = 9.62\n", - "890) Restarting with fbest = 5.87\n", - "Starting from fbest = 25.71\n" - ] - } - ], - "source": [ - "turbo1.optimize()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extract all evaluations from Turbo and print the best" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Best value found:\n", - "\tf(x) = 1.001\n", - "Observed at:\n", - "\tx = [-3.006 0.914 3.659 0.853 0.033 -0.203 1.199 0.812 -0.301 2.42 ]\n" - ] - } - ], - "source": [ - "X = turbo1.X # Evaluated points\n", - "fX = turbo1.fX # Observed values\n", - "ind_best = np.argmin(fX)\n", - "f_best, x_best = fX[ind_best], X[ind_best, :]\n", - "\n", - "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plot the progress\n", - "Each trust region is independent and finds different solutions" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "fig = plt.figure(figsize=(7, 5))\n", - "matplotlib.rcParams.update({'font.size': 16})\n", - "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", - "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", - "plt.xlim([0, len(fX)])\n", - "plt.ylim([0, 30])\n", - "plt.title(\"10D Levy function\")\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple example of TuRBO-1" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from turbo import Turbo1\n", + "import numpy as np\n", + "import torch\n", + "import math\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up an optimization problem class" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class Levy:\n", + " def __init__(self, dim=10):\n", + " self.dim = dim\n", + " self.lb = -5 * np.ones(dim)\n", + " self.ub = 10 * np.ones(dim)\n", + " \n", + " def __call__(self, x):\n", + " assert len(x) == self.dim\n", + " assert x.ndim == 1\n", + " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", + " w = 1 + (x - 1.0) / 4.0\n", + " val = np.sin(np.pi * w[0]) ** 2 + \\\n", + " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", + " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", + " return val\n", + "\n", + "f = Levy(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Turbo optimizer instance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using dtype = torch.float64 \n", + "Using device = cpu\n" + ] + } + ], + "source": [ + "turbo1 = Turbo1(\n", + " f=f, # Handle to objective function\n", + " lb=f.lb, # Numpy array specifying lower bounds\n", + " ub=f.ub, # Numpy array specifying upper bounds\n", + " n_init=20, # Number of initial bounds from an Latin hypercube design\n", + " max_evals = 1000, # Maximum number of evaluations\n", + " batch_size=10, # How large batch size TuRBO uses\n", + " verbose=True, # Print information from each batch\n", + " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", + " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", + " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", + " min_cuda=1024, # Run on the CPU for small datasets\n", + " device=\"cpu\", # \"cpu\" or \"cuda\"\n", + " dtype=\"float64\", # float64 or float32\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run the optimization process" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting from fbest = 20.98\n", + "50) New best: 15.65\n", + "80) New best: 11.27\n", + "90) New best: 9.325\n", + "100) New best: 8.288\n", + "110) New best: 6.944\n", + "120) New best: 5.974\n", + "140) New best: 5.951\n", + "160) New best: 5.905\n", + "170) New best: 5.905\n", + "180) New best: 5.822\n", + "190) New best: 5.785\n", + "200) New best: 5.759\n", + "220) New best: 5.738\n", + "230) New best: 5.683\n", + "240) Restarting with fbest = 5.683\n", + "Starting from fbest = 32.5\n", + "320) New best: 5.526\n", + "330) New best: 3.95\n", + "350) New best: 1.736\n", + "370) New best: 1.229\n", + "410) New best: 1.206\n", + "420) New best: 1.193\n", + "430) New best: 1.191\n", + "440) New best: 1.163\n", + "450) New best: 1.145\n", + "460) New best: 1.06\n", + "480) New best: 1.024\n", + "490) New best: 1.01\n", + "500) New best: 1.001\n", + "530) Restarting with fbest = 1.001\n", + "Starting from fbest = 12.85\n", + "730) Restarting with fbest = 8.634\n", + "Starting from fbest = 9.62\n", + "890) Restarting with fbest = 5.87\n", + "Starting from fbest = 25.71\n" + ] + } + ], + "source": [ + "turbo1.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract all evaluations from Turbo and print the best" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best value found:\n", + "\tf(x) = 1.001\n", + "Observed at:\n", + "\tx = [-3.006 0.914 3.659 0.853 0.033 -0.203 1.199 0.812 -0.301 2.42 ]\n" + ] + } + ], + "source": [ + "X = turbo1.X # Evaluated points\n", + "fX = turbo1.fX # Observed values\n", + "ind_best = np.argmin(fX)\n", + "f_best, x_best = fX[ind_best], X[ind_best, :]\n", + "\n", + "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot the progress\n", + "Each trust region is independent and finds different solutions" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(7, 5))\n", + "matplotlib.rcParams.update({'font.size': 16})\n", + "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", + "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", + "plt.xlim([0, len(fX)])\n", + "plt.ylim([0, 30])\n", + "plt.title(\"10D Levy function\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/mylib/lib_turbom/examples/TurboM.ipynb b/mylib/lib_turbom/examples/TurboM.ipynb index c5a632c..15e350e 100644 --- a/mylib/lib_turbom/examples/TurboM.ipynb +++ b/mylib/lib_turbom/examples/TurboM.ipynb @@ -1,247 +1,247 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simple example of TuRBO-m" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from turbo import TurboM\n", - "import numpy as np\n", - "import torch\n", - "import math\n", - "import matplotlib\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up an optimization problem class" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "class Levy:\n", - " def __init__(self, dim=10):\n", - " self.dim = dim\n", - " self.lb = -5 * np.ones(dim)\n", - " self.ub = 10 * np.ones(dim)\n", - " \n", - " def __call__(self, x):\n", - " assert len(x) == self.dim\n", - " assert x.ndim == 1\n", - " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", - " w = 1 + (x - 1.0) / 4.0\n", - " val = np.sin(np.pi * w[0]) ** 2 + \\\n", - " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", - " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", - " return val\n", - "\n", - "f = Levy(10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a Turbo optimizer instance" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using dtype = torch.float64 \n", - "Using device = cpu\n" - ] - } - ], - "source": [ - "turbo_m = TurboM(\n", - " f=f, # Handle to objective function\n", - " lb=f.lb, # Numpy array specifying lower bounds\n", - " ub=f.ub, # Numpy array specifying upper bounds\n", - " n_init=10, # Number of initial bounds from an Symmetric Latin hypercube design\n", - " max_evals=1000, # Maximum number of evaluations\n", - " n_trust_regions=5, # Number of trust regions\n", - " batch_size=10, # How large batch size TuRBO uses\n", - " verbose=True, # Print information from each batch\n", - " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", - " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", - " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", - " min_cuda=1024, # Run on the CPU for small datasets\n", - " device=\"cpu\", # \"cpu\" or \"cuda\"\n", - " dtype=\"float64\", # float64 or float32\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Run the optimization process" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TR-0 starting from: 24.79\n", - "TR-1 starting from: 20.77\n", - "TR-2 starting from: 14.87\n", - "TR-3 starting from: 27.97\n", - "TR-4 starting from: 23.89\n", - "80) New best @ TR-2: 12.43\n", - "90) New best @ TR-2: 6.42\n", - "110) New best @ TR-2: 5.467\n", - "180) New best @ TR-2: 2.888\n", - "230) New best @ TR-1: 1.944\n", - "280) New best @ TR-1: 1.54\n", - "310) New best @ TR-1: 1.052\n", - "340) New best @ TR-1: 1.038\n", - "390) New best @ TR-1: 0.9689\n", - "410) New best @ TR-1: 0.877\n", - "420) New best @ TR-1: 0.7794\n", - "460) New best @ TR-1: 0.7509\n", - "470) New best @ TR-1: 0.7264\n", - "480) New best @ TR-1: 0.7238\n", - "530) New best @ TR-1: 0.7044\n", - "540) New best @ TR-1: 0.695\n", - "550) New best @ TR-1: 0.6823\n", - "560) New best @ TR-1: 0.6656\n", - "590) New best @ TR-1: 0.6614\n", - "600) New best @ TR-1: 0.6604\n", - "640) TR-1 converged to: : 0.6604\n", - "640) TR-1 is restarting from: : 23.66\n" - ] - } - ], - "source": [ - "turbo_m.optimize()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extract all evaluations from Turbo and print the best" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Best value found:\n", - "\tf(x) = 0.660\n", - "Observed at:\n", - "\tx = [-2.968 1.072 0.173 0.973 3.698 0.883 0.946 0.872 0.006 0.927]\n" - ] - } - ], - "source": [ - "X = turbo_m.X # Evaluated points\n", - "fX = turbo_m.fX # Observed values\n", - "ind_best = np.argmin(fX)\n", - "f_best, x_best = fX[ind_best], X[ind_best, :]\n", - "\n", - "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plot the progress\n", - "\n", - "TuRBO-5 converges to a solution close to the global optimum" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "fig = plt.figure(figsize=(7, 5))\n", - "matplotlib.rcParams.update({'font.size': 16})\n", - "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", - "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", - "plt.xlim([0, len(fX)])\n", - "plt.ylim([0, 30])\n", - "plt.title(\"10D Levy function\")\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple example of TuRBO-m" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from turbo import TurboM\n", + "import numpy as np\n", + "import torch\n", + "import math\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up an optimization problem class" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class Levy:\n", + " def __init__(self, dim=10):\n", + " self.dim = dim\n", + " self.lb = -5 * np.ones(dim)\n", + " self.ub = 10 * np.ones(dim)\n", + " \n", + " def __call__(self, x):\n", + " assert len(x) == self.dim\n", + " assert x.ndim == 1\n", + " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", + " w = 1 + (x - 1.0) / 4.0\n", + " val = np.sin(np.pi * w[0]) ** 2 + \\\n", + " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", + " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", + " return val\n", + "\n", + "f = Levy(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Turbo optimizer instance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using dtype = torch.float64 \n", + "Using device = cpu\n" + ] + } + ], + "source": [ + "turbo_m = TurboM(\n", + " f=f, # Handle to objective function\n", + " lb=f.lb, # Numpy array specifying lower bounds\n", + " ub=f.ub, # Numpy array specifying upper bounds\n", + " n_init=10, # Number of initial bounds from an Symmetric Latin hypercube design\n", + " max_evals=1000, # Maximum number of evaluations\n", + " n_trust_regions=5, # Number of trust regions\n", + " batch_size=10, # How large batch size TuRBO uses\n", + " verbose=True, # Print information from each batch\n", + " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", + " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", + " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", + " min_cuda=1024, # Run on the CPU for small datasets\n", + " device=\"cpu\", # \"cpu\" or \"cuda\"\n", + " dtype=\"float64\", # float64 or float32\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run the optimization process" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TR-0 starting from: 24.79\n", + "TR-1 starting from: 20.77\n", + "TR-2 starting from: 14.87\n", + "TR-3 starting from: 27.97\n", + "TR-4 starting from: 23.89\n", + "80) New best @ TR-2: 12.43\n", + "90) New best @ TR-2: 6.42\n", + "110) New best @ TR-2: 5.467\n", + "180) New best @ TR-2: 2.888\n", + "230) New best @ TR-1: 1.944\n", + "280) New best @ TR-1: 1.54\n", + "310) New best @ TR-1: 1.052\n", + "340) New best @ TR-1: 1.038\n", + "390) New best @ TR-1: 0.9689\n", + "410) New best @ TR-1: 0.877\n", + "420) New best @ TR-1: 0.7794\n", + "460) New best @ TR-1: 0.7509\n", + "470) New best @ TR-1: 0.7264\n", + "480) New best @ TR-1: 0.7238\n", + "530) New best @ TR-1: 0.7044\n", + "540) New best @ TR-1: 0.695\n", + "550) New best @ TR-1: 0.6823\n", + "560) New best @ TR-1: 0.6656\n", + "590) New best @ TR-1: 0.6614\n", + "600) New best @ TR-1: 0.6604\n", + "640) TR-1 converged to: : 0.6604\n", + "640) TR-1 is restarting from: : 23.66\n" + ] + } + ], + "source": [ + "turbo_m.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract all evaluations from Turbo and print the best" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best value found:\n", + "\tf(x) = 0.660\n", + "Observed at:\n", + "\tx = [-2.968 1.072 0.173 0.973 3.698 0.883 0.946 0.872 0.006 0.927]\n" + ] + } + ], + "source": [ + "X = turbo_m.X # Evaluated points\n", + "fX = turbo_m.fX # Observed values\n", + "ind_best = np.argmin(fX)\n", + "f_best, x_best = fX[ind_best], X[ind_best, :]\n", + "\n", + "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot the progress\n", + "\n", + "TuRBO-5 converges to a solution close to the global optimum" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(7, 5))\n", + "matplotlib.rcParams.update({'font.size': 16})\n", + "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", + "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", + "plt.xlim([0, len(fX)])\n", + "plt.ylim([0, 30])\n", + "plt.title(\"10D Levy function\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/mylib/lib_turbom/requirements.txt b/mylib/lib_turbom/requirements.txt index 525eb63..8aea783 100644 --- a/mylib/lib_turbom/requirements.txt +++ b/mylib/lib_turbom/requirements.txt @@ -1,3 +1,3 @@ -numpy==1.17.3 -torch==1.3.0 -gpytorch==0.3.6 +numpy==1.17.3 +torch==1.3.0 +gpytorch==0.3.6 diff --git a/mylib/lib_turbom/setup.py b/mylib/lib_turbom/setup.py index bd5afd5..d1c7647 100644 --- a/mylib/lib_turbom/setup.py +++ b/mylib/lib_turbom/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup, find_packages - -setup( - name="turbo", - version="0.0.1", - packages=find_packages(), - install_requires=["numpy>=1.17.3", "torch>=1.3.0", "gpytorch>=0.3.6"], -) +from setuptools import setup, find_packages + +setup( + name="turbo", + version="0.0.1", + packages=find_packages(), + install_requires=["numpy>=1.17.3", "torch>=1.3.0", "gpytorch>=0.3.6"], +) diff --git a/mylib/lib_turbom/turbo/__init__.py b/mylib/lib_turbom/turbo/__init__.py index 6d17f20..eb0846a 100644 --- a/mylib/lib_turbom/turbo/__init__.py +++ b/mylib/lib_turbom/turbo/__init__.py @@ -1,2 +1,2 @@ -from .turbo_1 import Turbo1 -from .turbo_m import TurboM +from .turbo_1 import Turbo1 +from .turbo_m import TurboM diff --git a/mylib/lib_turbom/turbo/gp.py b/mylib/lib_turbom/turbo/gp.py index 873df9d..9f1b208 100644 --- a/mylib/lib_turbom/turbo/gp.py +++ b/mylib/lib_turbom/turbo/gp.py @@ -1,98 +1,98 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import math - -import gpytorch -import numpy as np -import torch -from gpytorch.constraints.constraints import Interval -from gpytorch.distributions import MultivariateNormal -from gpytorch.kernels import MaternKernel, ScaleKernel -from gpytorch.likelihoods import GaussianLikelihood -from gpytorch.means import ConstantMean -from gpytorch.mlls import ExactMarginalLogLikelihood -from gpytorch.models import ExactGP - - -# GP Model -class GP(ExactGP): - def __init__(self, train_x, train_y, likelihood, lengthscale_constraint, outputscale_constraint, ard_dims): - super(GP, self).__init__(train_x, train_y, likelihood) - self.ard_dims = ard_dims - self.mean_module = ConstantMean() - base_kernel = MaternKernel(lengthscale_constraint=lengthscale_constraint, ard_num_dims=ard_dims, nu=2.5) - self.covar_module = ScaleKernel(base_kernel, outputscale_constraint=outputscale_constraint) - - def forward(self, x): - mean_x = self.mean_module(x) - covar_x = self.covar_module(x) - return MultivariateNormal(mean_x, covar_x) - - -def train_gp(train_x, train_y, use_ard, num_steps, hypers={}): - """Fit a GP model where train_x is in [0, 1]^d and train_y is standardized.""" - assert train_x.ndim == 2 - assert train_y.ndim == 1 - assert train_x.shape[0] == train_y.shape[0] - - # Create hyper parameter bounds - noise_constraint = Interval(5e-4, 0.2) - if use_ard: - lengthscale_constraint = Interval(0.005, 2.0) - else: - lengthscale_constraint = Interval(0.005, math.sqrt(train_x.shape[1])) # [0.005, sqrt(dim)] - outputscale_constraint = Interval(0.05, 20.0) - - # Create models - likelihood = GaussianLikelihood(noise_constraint=noise_constraint).to(device=train_x.device, dtype=train_y.dtype) - ard_dims = train_x.shape[1] if use_ard else None - model = GP( - train_x=train_x, - train_y=train_y, - likelihood=likelihood, - lengthscale_constraint=lengthscale_constraint, - outputscale_constraint=outputscale_constraint, - ard_dims=ard_dims, - ).to(device=train_x.device, dtype=train_x.dtype) - - # Find optimal model hyperparameters - model.train() - likelihood.train() - - # "Loss" for GPs - the marginal log likelihood - mll = ExactMarginalLogLikelihood(likelihood, model) - - # Initialize model hypers - if hypers: - model.load_state_dict(hypers) - else: - hypers = {} - hypers["covar_module.outputscale"] = 1.0 - hypers["covar_module.base_kernel.lengthscale"] = 0.5 - hypers["likelihood.noise"] = 0.005 - model.initialize(**hypers) - - # Use the adam optimizer - optimizer = torch.optim.Adam([{"params": model.parameters()}], lr=0.1) - - for _ in range(num_steps): - optimizer.zero_grad() - output = model(train_x) - loss = -mll(output, train_y) - loss.backward() - optimizer.step() - - # Switch to eval mode - model.eval() - likelihood.eval() - - return model +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math + +import gpytorch +import numpy as np +import torch +from gpytorch.constraints.constraints import Interval +from gpytorch.distributions import MultivariateNormal +from gpytorch.kernels import MaternKernel, ScaleKernel +from gpytorch.likelihoods import GaussianLikelihood +from gpytorch.means import ConstantMean +from gpytorch.mlls import ExactMarginalLogLikelihood +from gpytorch.models import ExactGP + + +# GP Model +class GP(ExactGP): + def __init__(self, train_x, train_y, likelihood, lengthscale_constraint, outputscale_constraint, ard_dims): + super(GP, self).__init__(train_x, train_y, likelihood) + self.ard_dims = ard_dims + self.mean_module = ConstantMean() + base_kernel = MaternKernel(lengthscale_constraint=lengthscale_constraint, ard_num_dims=ard_dims, nu=2.5) + self.covar_module = ScaleKernel(base_kernel, outputscale_constraint=outputscale_constraint) + + def forward(self, x): + mean_x = self.mean_module(x) + covar_x = self.covar_module(x) + return MultivariateNormal(mean_x, covar_x) + + +def train_gp(train_x, train_y, use_ard, num_steps, hypers={}): + """Fit a GP model where train_x is in [0, 1]^d and train_y is standardized.""" + assert train_x.ndim == 2 + assert train_y.ndim == 1 + assert train_x.shape[0] == train_y.shape[0] + + # Create hyper parameter bounds + noise_constraint = Interval(5e-4, 0.2) + if use_ard: + lengthscale_constraint = Interval(0.005, 2.0) + else: + lengthscale_constraint = Interval(0.005, math.sqrt(train_x.shape[1])) # [0.005, sqrt(dim)] + outputscale_constraint = Interval(0.05, 20.0) + + # Create models + likelihood = GaussianLikelihood(noise_constraint=noise_constraint).to(device=train_x.device, dtype=train_y.dtype) + ard_dims = train_x.shape[1] if use_ard else None + model = GP( + train_x=train_x, + train_y=train_y, + likelihood=likelihood, + lengthscale_constraint=lengthscale_constraint, + outputscale_constraint=outputscale_constraint, + ard_dims=ard_dims, + ).to(device=train_x.device, dtype=train_x.dtype) + + # Find optimal model hyperparameters + model.train() + likelihood.train() + + # "Loss" for GPs - the marginal log likelihood + mll = ExactMarginalLogLikelihood(likelihood, model) + + # Initialize model hypers + if hypers: + model.load_state_dict(hypers) + else: + hypers = {} + hypers["covar_module.outputscale"] = 1.0 + hypers["covar_module.base_kernel.lengthscale"] = 0.5 + hypers["likelihood.noise"] = 0.005 + model.initialize(**hypers) + + # Use the adam optimizer + optimizer = torch.optim.Adam([{"params": model.parameters()}], lr=0.1) + + for _ in range(num_steps): + optimizer.zero_grad() + output = model(train_x) + loss = -mll(output, train_y) + loss.backward() + optimizer.step() + + # Switch to eval mode + model.eval() + likelihood.eval() + + return model diff --git a/mylib/lib_turbom/turbo/turbo_1.py b/mylib/lib_turbom/turbo/turbo_1.py index 7be0e92..6a756fc 100644 --- a/mylib/lib_turbom/turbo/turbo_1.py +++ b/mylib/lib_turbom/turbo/turbo_1.py @@ -1,301 +1,301 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import math -import sys -from copy import deepcopy - -import gpytorch -import numpy as np -import torch -from torch.quasirandom import SobolEngine - -from .gp import train_gp -from .utils import from_unit_cube, latin_hypercube, to_unit_cube - - -class Turbo1: - """The TuRBO-1 algorithm. - - Parameters - ---------- - f : function handle - lb : Lower variable bounds, numpy.array, shape (d,). - ub : Upper variable bounds, numpy.array, shape (d,). - n_init : Number of initial points (2*dim is recommended), int. - max_evals : Total evaluation budget, int. - batch_size : Number of points in each batch, int. - verbose : If you want to print information about the optimization progress, bool. - use_ard : If you want to use ARD for the GP kernel. - max_cholesky_size : Largest number of training points where we use Cholesky, int - n_training_steps : Number of training steps for learning the GP hypers, int - min_cuda : We use float64 on the CPU if we have this or fewer datapoints - device : Device to use for GP fitting ("cpu" or "cuda") - dtype : Dtype to use for GP fitting ("float32" or "float64") - - Example usage: - turbo1 = Turbo1(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals) - turbo1.optimize() # Run optimization - X, fX = turbo1.X, turbo1.fX # Evaluated points - """ - - def __init__( - self, - f, - lb, - ub, - n_init, - max_evals, - batch_size=1, - verbose=True, - use_ard=True, - max_cholesky_size=2000, - n_training_steps=50, - min_cuda=1024, - device="cpu", - dtype="float64", - ): - - # Very basic input checks - assert lb.ndim == 1 and ub.ndim == 1 - assert len(lb) == len(ub) - assert np.all(ub > lb) - assert max_evals > 0 and isinstance(max_evals, int) - assert n_init > 0 and isinstance(n_init, int) - assert batch_size > 0 and isinstance(batch_size, int) - assert isinstance(verbose, bool) and isinstance(use_ard, bool) - assert max_cholesky_size >= 0 and isinstance(batch_size, int) - assert n_training_steps >= 30 and isinstance(n_training_steps, int) - assert max_evals > n_init and max_evals > batch_size - assert device == "cpu" or device == "cuda" - assert dtype == "float32" or dtype == "float64" - if device == "cuda": - assert torch.cuda.is_available(), "can't use cuda if it's not available" - - # Save function information - self.f = f - self.dim = len(lb) - self.lb = lb - self.ub = ub - - # Settings - self.n_init = n_init - self.max_evals = max_evals - self.batch_size = batch_size - self.verbose = verbose - self.use_ard = use_ard - self.max_cholesky_size = max_cholesky_size - self.n_training_steps = n_training_steps - - # Hyperparameters - self.mean = np.zeros((0, 1)) - self.signal_var = np.zeros((0, 1)) - self.noise_var = np.zeros((0, 1)) - self.lengthscales = np.zeros((0, self.dim)) if self.use_ard else np.zeros((0, 1)) - - # Tolerances and counters - self.n_cand = min(100 * self.dim, 5000) - self.failtol = np.ceil(np.max([4.0 / batch_size, self.dim / batch_size])) - self.succtol = 3 - self.n_evals = 0 - - # Trust region sizes - self.length_min = 0.5 ** 7 - self.length_max = 1.6 - self.length_init = 0.8 - - # Save the full history - self.X = np.zeros((0, self.dim)) - self.fX = np.zeros((0, 1)) - - # Device and dtype for GPyTorch - self.min_cuda = min_cuda - self.dtype = torch.float32 if dtype == "float32" else torch.float64 - self.device = torch.device("cuda") if device == "cuda" else torch.device("cpu") - if self.verbose: - print("Using dtype = %s \nUsing device = %s" % (self.dtype, self.device)) - sys.stdout.flush() - - # Initialize parameters - self._restart() - - def _restart(self): - self._X = [] - self._fX = [] - self.failcount = 0 - self.succcount = 0 - self.length = self.length_init - - def _adjust_length(self, fX_next): - if np.min(fX_next) < np.min(self._fX) - 1e-3 * math.fabs(np.min(self._fX)): - self.succcount += 1 - self.failcount = 0 - else: - self.succcount = 0 - self.failcount += 1 - - if self.succcount == self.succtol: # Expand trust region - self.length = min([2.0 * self.length, self.length_max]) - self.succcount = 0 - elif self.failcount == self.failtol: # Shrink trust region - self.length /= 2.0 - self.failcount = 0 - - def _create_candidates(self, X, fX, length, n_training_steps, hypers): - """Generate candidates assuming X has been scaled to [0,1]^d.""" - # Pick the center as the point with the smallest function values - # NOTE: This may not be robust to noise, in which case the posterior mean of the GP can be used instead - assert X.min() >= 0.0 and X.max() <= 1.0 - - # Standardize function values. - mu, sigma = np.median(fX), fX.std() - sigma = 1.0 if sigma < 1e-6 else sigma - fX = (deepcopy(fX) - mu) / sigma - - # Figure out what device we are running on - if len(X) < self.min_cuda: - device, dtype = torch.device("cpu"), torch.float64 - else: - device, dtype = self.device, self.dtype - - # We use CG + Lanczos for training if we have enough data - with gpytorch.settings.max_cholesky_size(self.max_cholesky_size): - X_torch = torch.tensor(X).to(device=device, dtype=dtype) - y_torch = torch.tensor(fX).to(device=device, dtype=dtype) - gp = train_gp( - train_x=X_torch, train_y=y_torch, use_ard=self.use_ard, num_steps=n_training_steps, hypers=hypers - ) - - # Save state dict - hypers = gp.state_dict() - - # Create the trust region boundaries - x_center = X[fX.argmin().item(), :][None, :] - weights = gp.covar_module.base_kernel.lengthscale.cpu().detach().numpy().ravel() - weights = weights / weights.mean() # This will make the next line more stable - weights = weights / np.prod(np.power(weights, 1.0 / len(weights))) # We now have weights.prod() = 1 - lb = np.clip(x_center - weights * length / 2.0, 0.0, 1.0) - ub = np.clip(x_center + weights * length / 2.0, 0.0, 1.0) - - # Draw a Sobolev sequence in [lb, ub] - seed = np.random.randint(int(1e6)) - sobol = SobolEngine(self.dim, scramble=True, seed=seed) - pert = sobol.draw(self.n_cand).to(dtype=dtype, device=device).cpu().detach().numpy() - pert = lb + (ub - lb) * pert - - # Create a perturbation mask - prob_perturb = min(20.0 / self.dim, 1.0) - mask = np.random.rand(self.n_cand, self.dim) <= prob_perturb - ind = np.where(np.sum(mask, axis=1) == 0)[0] - mask[ind, np.random.randint(0, self.dim - 1, size=len(ind))] = 1 - - # Create candidate points - X_cand = x_center.copy() * np.ones((self.n_cand, self.dim)) - X_cand[mask] = pert[mask] - - # Figure out what device we are running on - if len(X_cand) < self.min_cuda: - device, dtype = torch.device("cpu"), torch.float64 - else: - device, dtype = self.device, self.dtype - - # We may have to move the GP to a new device - gp = gp.to(dtype=dtype, device=device) - - # We use Lanczos for sampling if we have enough data - with torch.no_grad(), gpytorch.settings.max_cholesky_size(self.max_cholesky_size): - X_cand_torch = torch.tensor(X_cand).to(device=device, dtype=dtype) - y_cand = gp.likelihood(gp(X_cand_torch)).sample(torch.Size([self.batch_size])).t().cpu().detach().numpy() - - # Remove the torch variables - del X_torch, y_torch, X_cand_torch, gp - - # De-standardize the sampled values - y_cand = mu + sigma * y_cand - - return X_cand, y_cand, hypers - - def _select_candidates(self, X_cand, y_cand): - """Select candidates.""" - X_next = np.ones((self.batch_size, self.dim)) - for i in range(self.batch_size): - # Pick the best point and make sure we never pick it again - indbest = np.argmin(y_cand[:, i]) - X_next[i, :] = deepcopy(X_cand[indbest, :]) - y_cand[indbest, :] = np.inf - return X_next - - def optimize(self): - """Run the full optimization process.""" - while self.n_evals < self.max_evals: - if len(self._fX) > 0 and self.verbose: - n_evals, fbest = self.n_evals, self._fX.min() - print(f"{n_evals}) Restarting with fbest = {fbest:.4}") - sys.stdout.flush() - - # Initialize parameters - self._restart() - - # Generate and evalute initial design points - X_init = latin_hypercube(self.n_init, self.dim) - X_init = from_unit_cube(X_init, self.lb, self.ub) - fX_init = np.array([[self.f(x)] for x in X_init]) - - # Update budget and set as initial data for this TR - self.n_evals += self.n_init - self._X = deepcopy(X_init) - self._fX = deepcopy(fX_init) - - # Append data to the global history - self.X = np.vstack((self.X, deepcopy(X_init))) - self.fX = np.vstack((self.fX, deepcopy(fX_init))) - - if self.verbose: - fbest = self._fX.min() - print(f"Starting from fbest = {fbest:.4}") - sys.stdout.flush() - - # Thompson sample to get next suggestions - while self.n_evals < self.max_evals and self.length >= self.length_min: - # Warp inputs - X = to_unit_cube(deepcopy(self._X), self.lb, self.ub) - - # Standardize values - fX = deepcopy(self._fX).ravel() - - # Create th next batch - X_cand, y_cand, _ = self._create_candidates( - X, fX, length=self.length, n_training_steps=self.n_training_steps, hypers={} - ) - X_next = self._select_candidates(X_cand, y_cand) - - # Undo the warping - X_next = from_unit_cube(X_next, self.lb, self.ub) - - # Evaluate batch - fX_next = np.array([[self.f(x)] for x in X_next]) - - # Update trust region - self._adjust_length(fX_next) - - # Update budget and append data - self.n_evals += self.batch_size - self._X = np.vstack((self._X, X_next)) - self._fX = np.vstack((self._fX, fX_next)) - - if self.verbose and fX_next.min() < self.fX.min(): - n_evals, fbest = self.n_evals, fX_next.min() - print(f"{n_evals}) New best: {fbest:.4}") - sys.stdout.flush() - - # Append data to the global history - self.X = np.vstack((self.X, deepcopy(X_next))) - self.fX = np.vstack((self.fX, deepcopy(fX_next))) +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math +import sys +from copy import deepcopy + +import gpytorch +import numpy as np +import torch +from torch.quasirandom import SobolEngine + +from .gp import train_gp +from .utils import from_unit_cube, latin_hypercube, to_unit_cube + + +class Turbo1: + """The TuRBO-1 algorithm. + + Parameters + ---------- + f : function handle + lb : Lower variable bounds, numpy.array, shape (d,). + ub : Upper variable bounds, numpy.array, shape (d,). + n_init : Number of initial points (2*dim is recommended), int. + max_evals : Total evaluation budget, int. + batch_size : Number of points in each batch, int. + verbose : If you want to print information about the optimization progress, bool. + use_ard : If you want to use ARD for the GP kernel. + max_cholesky_size : Largest number of training points where we use Cholesky, int + n_training_steps : Number of training steps for learning the GP hypers, int + min_cuda : We use float64 on the CPU if we have this or fewer datapoints + device : Device to use for GP fitting ("cpu" or "cuda") + dtype : Dtype to use for GP fitting ("float32" or "float64") + + Example usage: + turbo1 = Turbo1(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals) + turbo1.optimize() # Run optimization + X, fX = turbo1.X, turbo1.fX # Evaluated points + """ + + def __init__( + self, + f, + lb, + ub, + n_init, + max_evals, + batch_size=1, + verbose=True, + use_ard=True, + max_cholesky_size=2000, + n_training_steps=50, + min_cuda=1024, + device="cpu", + dtype="float64", + ): + + # Very basic input checks + assert lb.ndim == 1 and ub.ndim == 1 + assert len(lb) == len(ub) + assert np.all(ub > lb) + assert max_evals > 0 and isinstance(max_evals, int) + assert n_init > 0 and isinstance(n_init, int) + assert batch_size > 0 and isinstance(batch_size, int) + assert isinstance(verbose, bool) and isinstance(use_ard, bool) + assert max_cholesky_size >= 0 and isinstance(batch_size, int) + assert n_training_steps >= 30 and isinstance(n_training_steps, int) + assert max_evals > n_init and max_evals > batch_size + assert device == "cpu" or device == "cuda" + assert dtype == "float32" or dtype == "float64" + if device == "cuda": + assert torch.cuda.is_available(), "can't use cuda if it's not available" + + # Save function information + self.f = f + self.dim = len(lb) + self.lb = lb + self.ub = ub + + # Settings + self.n_init = n_init + self.max_evals = max_evals + self.batch_size = batch_size + self.verbose = verbose + self.use_ard = use_ard + self.max_cholesky_size = max_cholesky_size + self.n_training_steps = n_training_steps + + # Hyperparameters + self.mean = np.zeros((0, 1)) + self.signal_var = np.zeros((0, 1)) + self.noise_var = np.zeros((0, 1)) + self.lengthscales = np.zeros((0, self.dim)) if self.use_ard else np.zeros((0, 1)) + + # Tolerances and counters + self.n_cand = min(100 * self.dim, 5000) + self.failtol = np.ceil(np.max([4.0 / batch_size, self.dim / batch_size])) + self.succtol = 3 + self.n_evals = 0 + + # Trust region sizes + self.length_min = 0.5 ** 7 + self.length_max = 1.6 + self.length_init = 0.8 + + # Save the full history + self.X = np.zeros((0, self.dim)) + self.fX = np.zeros((0, 1)) + + # Device and dtype for GPyTorch + self.min_cuda = min_cuda + self.dtype = torch.float32 if dtype == "float32" else torch.float64 + self.device = torch.device("cuda") if device == "cuda" else torch.device("cpu") + if self.verbose: + print("Using dtype = %s \nUsing device = %s" % (self.dtype, self.device)) + sys.stdout.flush() + + # Initialize parameters + self._restart() + + def _restart(self): + self._X = [] + self._fX = [] + self.failcount = 0 + self.succcount = 0 + self.length = self.length_init + + def _adjust_length(self, fX_next): + if np.min(fX_next) < np.min(self._fX) - 1e-3 * math.fabs(np.min(self._fX)): + self.succcount += 1 + self.failcount = 0 + else: + self.succcount = 0 + self.failcount += 1 + + if self.succcount == self.succtol: # Expand trust region + self.length = min([2.0 * self.length, self.length_max]) + self.succcount = 0 + elif self.failcount == self.failtol: # Shrink trust region + self.length /= 2.0 + self.failcount = 0 + + def _create_candidates(self, X, fX, length, n_training_steps, hypers): + """Generate candidates assuming X has been scaled to [0,1]^d.""" + # Pick the center as the point with the smallest function values + # NOTE: This may not be robust to noise, in which case the posterior mean of the GP can be used instead + assert X.min() >= 0.0 and X.max() <= 1.0 + + # Standardize function values. + mu, sigma = np.median(fX), fX.std() + sigma = 1.0 if sigma < 1e-6 else sigma + fX = (deepcopy(fX) - mu) / sigma + + # Figure out what device we are running on + if len(X) < self.min_cuda: + device, dtype = torch.device("cpu"), torch.float64 + else: + device, dtype = self.device, self.dtype + + # We use CG + Lanczos for training if we have enough data + with gpytorch.settings.max_cholesky_size(self.max_cholesky_size): + X_torch = torch.tensor(X).to(device=device, dtype=dtype) + y_torch = torch.tensor(fX).to(device=device, dtype=dtype) + gp = train_gp( + train_x=X_torch, train_y=y_torch, use_ard=self.use_ard, num_steps=n_training_steps, hypers=hypers + ) + + # Save state dict + hypers = gp.state_dict() + + # Create the trust region boundaries + x_center = X[fX.argmin().item(), :][None, :] + weights = gp.covar_module.base_kernel.lengthscale.cpu().detach().numpy().ravel() + weights = weights / weights.mean() # This will make the next line more stable + weights = weights / np.prod(np.power(weights, 1.0 / len(weights))) # We now have weights.prod() = 1 + lb = np.clip(x_center - weights * length / 2.0, 0.0, 1.0) + ub = np.clip(x_center + weights * length / 2.0, 0.0, 1.0) + + # Draw a Sobolev sequence in [lb, ub] + seed = np.random.randint(int(1e6)) + sobol = SobolEngine(self.dim, scramble=True, seed=seed) + pert = sobol.draw(self.n_cand).to(dtype=dtype, device=device).cpu().detach().numpy() + pert = lb + (ub - lb) * pert + + # Create a perturbation mask + prob_perturb = min(20.0 / self.dim, 1.0) + mask = np.random.rand(self.n_cand, self.dim) <= prob_perturb + ind = np.where(np.sum(mask, axis=1) == 0)[0] + mask[ind, np.random.randint(0, self.dim - 1, size=len(ind))] = 1 + + # Create candidate points + X_cand = x_center.copy() * np.ones((self.n_cand, self.dim)) + X_cand[mask] = pert[mask] + + # Figure out what device we are running on + if len(X_cand) < self.min_cuda: + device, dtype = torch.device("cpu"), torch.float64 + else: + device, dtype = self.device, self.dtype + + # We may have to move the GP to a new device + gp = gp.to(dtype=dtype, device=device) + + # We use Lanczos for sampling if we have enough data + with torch.no_grad(), gpytorch.settings.max_cholesky_size(self.max_cholesky_size): + X_cand_torch = torch.tensor(X_cand).to(device=device, dtype=dtype) + y_cand = gp.likelihood(gp(X_cand_torch)).sample(torch.Size([self.batch_size])).t().cpu().detach().numpy() + + # Remove the torch variables + del X_torch, y_torch, X_cand_torch, gp + + # De-standardize the sampled values + y_cand = mu + sigma * y_cand + + return X_cand, y_cand, hypers + + def _select_candidates(self, X_cand, y_cand): + """Select candidates.""" + X_next = np.ones((self.batch_size, self.dim)) + for i in range(self.batch_size): + # Pick the best point and make sure we never pick it again + indbest = np.argmin(y_cand[:, i]) + X_next[i, :] = deepcopy(X_cand[indbest, :]) + y_cand[indbest, :] = np.inf + return X_next + + def optimize(self): + """Run the full optimization process.""" + while self.n_evals < self.max_evals: + if len(self._fX) > 0 and self.verbose: + n_evals, fbest = self.n_evals, self._fX.min() + print(f"{n_evals}) Restarting with fbest = {fbest:.4}") + sys.stdout.flush() + + # Initialize parameters + self._restart() + + # Generate and evalute initial design points + X_init = latin_hypercube(self.n_init, self.dim) + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Update budget and set as initial data for this TR + self.n_evals += self.n_init + self._X = deepcopy(X_init) + self._fX = deepcopy(fX_init) + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_init))) + self.fX = np.vstack((self.fX, deepcopy(fX_init))) + + if self.verbose: + fbest = self._fX.min() + print(f"Starting from fbest = {fbest:.4}") + sys.stdout.flush() + + # Thompson sample to get next suggestions + while self.n_evals < self.max_evals and self.length >= self.length_min: + # Warp inputs + X = to_unit_cube(deepcopy(self._X), self.lb, self.ub) + + # Standardize values + fX = deepcopy(self._fX).ravel() + + # Create th next batch + X_cand, y_cand, _ = self._create_candidates( + X, fX, length=self.length, n_training_steps=self.n_training_steps, hypers={} + ) + X_next = self._select_candidates(X_cand, y_cand) + + # Undo the warping + X_next = from_unit_cube(X_next, self.lb, self.ub) + + # Evaluate batch + fX_next = np.array([[self.f(x)] for x in X_next]) + + # Update trust region + self._adjust_length(fX_next) + + # Update budget and append data + self.n_evals += self.batch_size + self._X = np.vstack((self._X, X_next)) + self._fX = np.vstack((self._fX, fX_next)) + + if self.verbose and fX_next.min() < self.fX.min(): + n_evals, fbest = self.n_evals, fX_next.min() + print(f"{n_evals}) New best: {fbest:.4}") + sys.stdout.flush() + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_next))) + self.fX = np.vstack((self.fX, deepcopy(fX_next))) diff --git a/mylib/lib_turbom/turbo/turbo_m.py b/mylib/lib_turbom/turbo/turbo_m.py index facd5fb..3d21334 100644 --- a/mylib/lib_turbom/turbo/turbo_m.py +++ b/mylib/lib_turbom/turbo/turbo_m.py @@ -1,253 +1,253 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import math -import sys -from copy import deepcopy - -import gpytorch -import numpy as np -import torch - -from .gp import train_gp -from .turbo_1 import Turbo1 -from .utils import from_unit_cube, latin_hypercube, to_unit_cube -import time - -class TurboM(Turbo1): - """The TuRBO-m algorithm. - - Parameters - ---------- - f : function handle - lb : Lower variable bounds, numpy.array, shape (d,). - ub : Upper variable bounds, numpy.array, shape (d,). - n_init : Number of initial points *FOR EACH TRUST REGION* (2*dim is recommended), int. - max_evals : Total evaluation budget, int. - n_trust_regions : Number of trust regions - batch_size : Number of points in each batch, int. - verbose : If you want to print information about the optimization progress, bool. - use_ard : If you want to use ARD for the GP kernel. - max_cholesky_size : Largest number of training points where we use Cholesky, int - n_training_steps : Number of training steps for learning the GP hypers, int - min_cuda : We use float64 on the CPU if we have this or fewer datapoints - device : Device to use for GP fitting ("cpu" or "cuda") - dtype : Dtype to use for GP fitting ("float32" or "float64") - - Example usage: - turbo5 = TurboM(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals, n_trust_regions=5) - turbo5.optimize() # Run optimization - X, fX = turbo5.X, turbo5.fX # Evaluated points - """ - - def __init__( - self, - f, - lb, - ub, - n_init, - max_evals, - n_trust_regions, - batch_size=1, - verbose=True, - use_ard=True, - max_cholesky_size=2000, - n_training_steps=50, - min_cuda=1024, - device="cpu", - dtype="float64", - ): - self.n_trust_regions = n_trust_regions - self.acq_opt_time = 0 - self.mode_fit_time = 0 - self.cum_iteration_time = 0 - super().__init__( - f=f, - lb=lb, - ub=ub, - n_init=n_init, - max_evals=max_evals, - batch_size=batch_size, - verbose=verbose, - use_ard=use_ard, - max_cholesky_size=max_cholesky_size, - n_training_steps=n_training_steps, - min_cuda=min_cuda, - device=device, - dtype=dtype, - ) - - self.succtol = 3 - self.failtol = max(5, self.dim) - - # Very basic input checks - assert n_trust_regions > 1 and isinstance(max_evals, int) - assert max_evals > n_trust_regions * n_init, "Not enough trust regions to do initial evaluations" - assert max_evals > batch_size, "Not enough evaluations to do a single batch" - - # Remember the hypers for trust regions we don't sample from - self.hypers = [{} for _ in range(self.n_trust_regions)] - - # Initialize parameters - self._restart() - - def _restart(self): - self._idx = np.zeros((0, 1), dtype=int) # Track what trust region proposed what using an index vector - self.failcount = np.zeros(self.n_trust_regions, dtype=int) - self.succcount = np.zeros(self.n_trust_regions, dtype=int) - self.length = self.length_init * np.ones(self.n_trust_regions) - - def _adjust_length(self, fX_next, i): - assert i >= 0 and i <= self.n_trust_regions - 1 - - fX_min = self.fX[self._idx[:, 0] == i, 0].min() # Target value - if fX_next.min() < fX_min - 1e-3 * math.fabs(fX_min): - self.succcount[i] += 1 - self.failcount[i] = 0 - else: - self.succcount[i] = 0 - self.failcount[i] += len(fX_next) # NOTE: Add size of the batch for this TR - - if self.succcount[i] == self.succtol: # Expand trust region - self.length[i] = min([2.0 * self.length[i], self.length_max]) - self.succcount[i] = 0 - elif self.failcount[i] >= self.failtol: # Shrink trust region (we may have exceeded the failtol) - self.length[i] /= 2.0 - self.failcount[i] = 0 - - def _select_candidates(self, X_cand, y_cand): - """Select candidates from samples from all trust regions.""" - assert X_cand.shape == (self.n_trust_regions, self.n_cand, self.dim) - assert y_cand.shape == (self.n_trust_regions, self.n_cand, self.batch_size) - assert X_cand.min() >= 0.0 and X_cand.max() <= 1.0 and np.all(np.isfinite(y_cand)) - - X_next = np.zeros((self.batch_size, self.dim)) - idx_next = np.zeros((self.batch_size, 1), dtype=int) - for k in range(self.batch_size): - i, j = np.unravel_index(np.argmin(y_cand[:, :, k]), (self.n_trust_regions, self.n_cand)) - assert y_cand[:, :, k].min() == y_cand[i, j, k] - X_next[k, :] = deepcopy(X_cand[i, j, :]) - idx_next[k, 0] = i - assert np.isfinite(y_cand[i, j, k]) # Just to make sure we never select nan or inf - - # Make sure we never pick this point again - y_cand[i, j, :] = np.inf - - return X_next, idx_next - - def optimize(self): - """Run the full optimization process.""" - # Create initial points for each TR - for i in range(self.n_trust_regions): - X_init = latin_hypercube(self.n_init, self.dim) - X_init = from_unit_cube(X_init, self.lb, self.ub) - fX_init = np.array([[self.f(x)] for x in X_init]) - - # Update budget and set as initial data for this TR - self.X = np.vstack((self.X, X_init)) - self.fX = np.vstack((self.fX, fX_init)) - self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) - self.n_evals += self.n_init - - if self.verbose: - fbest = fX_init.min() - print(f"TR-{i} starting from: {fbest:.4}") - sys.stdout.flush() - - # Thompson sample to get next suggestions - while self.n_evals < self.max_evals: - - # Generate candidates from each TR - X_cand = np.zeros((self.n_trust_regions, self.n_cand, self.dim)) - y_cand = np.inf * np.ones((self.n_trust_regions, self.n_cand, self.batch_size)) - start = time.process_time() - for i in range(self.n_trust_regions): - idx = np.where(self._idx == i)[0] # Extract all "active" indices - - # Get the points, values the active values - X = deepcopy(self.X[idx, :]) - X = to_unit_cube(X, self.lb, self.ub) - - # Get the values from the standardized data - fX = deepcopy(self.fX[idx, 0].ravel()) - - # Don't retrain the model if the training data hasn't changed - n_training_steps = 0 if self.hypers[i] else self.n_training_steps - - # Create new candidates - X_cand[i, :, :], y_cand[i, :, :], self.hypers[i] = self._create_candidates( - X, fX, length=self.length[i], n_training_steps=n_training_steps, hypers=self.hypers[i] - ) - self.mode_fit_time = time.process_time() - start - start = time.process_time() - # Select the next candidates - X_next, idx_next = self._select_candidates(X_cand, y_cand) - assert X_next.min() >= 0.0 and X_next.max() <= 1.0 - self.acq_opt_time = time.process_time() - start - # Undo the warping - X_next = from_unit_cube(X_next, self.lb, self.ub) - - # Evaluate batch - fX_next = np.array([[self.f(x)] for x in X_next]) - - # Update trust regions - for i in range(self.n_trust_regions): - idx_i = np.where(idx_next == i)[0] - if len(idx_i) > 0: - self.hypers[i] = {} # Remove model hypers - fX_i = fX_next[idx_i] - - if self.verbose and fX_i.min() < self.fX.min() - 1e-3 * math.fabs(self.fX.min()): - n_evals, fbest = self.n_evals, fX_i.min() - print(f"{n_evals}) New best @ TR-{i}: {fbest:.4}") - sys.stdout.flush() - self._adjust_length(fX_i, i) - - # Update budget and append data - self.n_evals += self.batch_size - self.X = np.vstack((self.X, deepcopy(X_next))) - self.fX = np.vstack((self.fX, deepcopy(fX_next))) - self._idx = np.vstack((self._idx, deepcopy(idx_next))) - - # Check if any TR needs to be restarted - for i in range(self.n_trust_regions): - if self.length[i] < self.length_min: # Restart trust region if converged - idx_i = self._idx[:, 0] == i - - if self.verbose: - n_evals, fbest = self.n_evals, self.fX[idx_i, 0].min() - print(f"{n_evals}) TR-{i} converged to: : {fbest:.4}") - sys.stdout.flush() - - # Reset length and counters, remove old data from trust region - self.length[i] = self.length_init - self.succcount[i] = 0 - self.failcount[i] = 0 - self._idx[idx_i, 0] = -1 # Remove points from trust region - self.hypers[i] = {} # Remove model hypers - - # Create a new initial design - X_init = latin_hypercube(self.n_init, self.dim) - X_init = from_unit_cube(X_init, self.lb, self.ub) - fX_init = np.array([[self.f(x)] for x in X_init]) - - # Print progress - if self.verbose: - n_evals, fbest = self.n_evals, fX_init.min() - print(f"{n_evals}) TR-{i} is restarting from: : {fbest:.4}") - sys.stdout.flush() - - # Append data to local history - self.X = np.vstack((self.X, X_init)) - self.fX = np.vstack((self.fX, fX_init)) - self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) - self.n_evals += self.n_init +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math +import sys +from copy import deepcopy + +import gpytorch +import numpy as np +import torch + +from .gp import train_gp +from .turbo_1 import Turbo1 +from .utils import from_unit_cube, latin_hypercube, to_unit_cube +import time + +class TurboM(Turbo1): + """The TuRBO-m algorithm. + + Parameters + ---------- + f : function handle + lb : Lower variable bounds, numpy.array, shape (d,). + ub : Upper variable bounds, numpy.array, shape (d,). + n_init : Number of initial points *FOR EACH TRUST REGION* (2*dim is recommended), int. + max_evals : Total evaluation budget, int. + n_trust_regions : Number of trust regions + batch_size : Number of points in each batch, int. + verbose : If you want to print information about the optimization progress, bool. + use_ard : If you want to use ARD for the GP kernel. + max_cholesky_size : Largest number of training points where we use Cholesky, int + n_training_steps : Number of training steps for learning the GP hypers, int + min_cuda : We use float64 on the CPU if we have this or fewer datapoints + device : Device to use for GP fitting ("cpu" or "cuda") + dtype : Dtype to use for GP fitting ("float32" or "float64") + + Example usage: + turbo5 = TurboM(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals, n_trust_regions=5) + turbo5.optimize() # Run optimization + X, fX = turbo5.X, turbo5.fX # Evaluated points + """ + + def __init__( + self, + f, + lb, + ub, + n_init, + max_evals, + n_trust_regions, + batch_size=1, + verbose=True, + use_ard=True, + max_cholesky_size=2000, + n_training_steps=50, + min_cuda=1024, + device="cpu", + dtype="float64", + ): + self.n_trust_regions = n_trust_regions + self.acq_opt_time = 0 + self.mode_fit_time = 0 + self.cum_iteration_time = 0 + super().__init__( + f=f, + lb=lb, + ub=ub, + n_init=n_init, + max_evals=max_evals, + batch_size=batch_size, + verbose=verbose, + use_ard=use_ard, + max_cholesky_size=max_cholesky_size, + n_training_steps=n_training_steps, + min_cuda=min_cuda, + device=device, + dtype=dtype, + ) + + self.succtol = 3 + self.failtol = max(5, self.dim) + + # Very basic input checks + assert n_trust_regions > 1 and isinstance(max_evals, int) + assert max_evals > n_trust_regions * n_init, "Not enough trust regions to do initial evaluations" + assert max_evals > batch_size, "Not enough evaluations to do a single batch" + + # Remember the hypers for trust regions we don't sample from + self.hypers = [{} for _ in range(self.n_trust_regions)] + + # Initialize parameters + self._restart() + + def _restart(self): + self._idx = np.zeros((0, 1), dtype=int) # Track what trust region proposed what using an index vector + self.failcount = np.zeros(self.n_trust_regions, dtype=int) + self.succcount = np.zeros(self.n_trust_regions, dtype=int) + self.length = self.length_init * np.ones(self.n_trust_regions) + + def _adjust_length(self, fX_next, i): + assert i >= 0 and i <= self.n_trust_regions - 1 + + fX_min = self.fX[self._idx[:, 0] == i, 0].min() # Target value + if fX_next.min() < fX_min - 1e-3 * math.fabs(fX_min): + self.succcount[i] += 1 + self.failcount[i] = 0 + else: + self.succcount[i] = 0 + self.failcount[i] += len(fX_next) # NOTE: Add size of the batch for this TR + + if self.succcount[i] == self.succtol: # Expand trust region + self.length[i] = min([2.0 * self.length[i], self.length_max]) + self.succcount[i] = 0 + elif self.failcount[i] >= self.failtol: # Shrink trust region (we may have exceeded the failtol) + self.length[i] /= 2.0 + self.failcount[i] = 0 + + def _select_candidates(self, X_cand, y_cand): + """Select candidates from samples from all trust regions.""" + assert X_cand.shape == (self.n_trust_regions, self.n_cand, self.dim) + assert y_cand.shape == (self.n_trust_regions, self.n_cand, self.batch_size) + assert X_cand.min() >= 0.0 and X_cand.max() <= 1.0 and np.all(np.isfinite(y_cand)) + + X_next = np.zeros((self.batch_size, self.dim)) + idx_next = np.zeros((self.batch_size, 1), dtype=int) + for k in range(self.batch_size): + i, j = np.unravel_index(np.argmin(y_cand[:, :, k]), (self.n_trust_regions, self.n_cand)) + assert y_cand[:, :, k].min() == y_cand[i, j, k] + X_next[k, :] = deepcopy(X_cand[i, j, :]) + idx_next[k, 0] = i + assert np.isfinite(y_cand[i, j, k]) # Just to make sure we never select nan or inf + + # Make sure we never pick this point again + y_cand[i, j, :] = np.inf + + return X_next, idx_next + + def optimize(self): + """Run the full optimization process.""" + # Create initial points for each TR + for i in range(self.n_trust_regions): + X_init = latin_hypercube(self.n_init, self.dim) + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Update budget and set as initial data for this TR + self.X = np.vstack((self.X, X_init)) + self.fX = np.vstack((self.fX, fX_init)) + self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) + self.n_evals += self.n_init + + if self.verbose: + fbest = fX_init.min() + print(f"TR-{i} starting from: {fbest:.4}") + sys.stdout.flush() + + # Thompson sample to get next suggestions + while self.n_evals < self.max_evals: + + # Generate candidates from each TR + X_cand = np.zeros((self.n_trust_regions, self.n_cand, self.dim)) + y_cand = np.inf * np.ones((self.n_trust_regions, self.n_cand, self.batch_size)) + start = time.process_time() + for i in range(self.n_trust_regions): + idx = np.where(self._idx == i)[0] # Extract all "active" indices + + # Get the points, values the active values + X = deepcopy(self.X[idx, :]) + X = to_unit_cube(X, self.lb, self.ub) + + # Get the values from the standardized data + fX = deepcopy(self.fX[idx, 0].ravel()) + + # Don't retrain the model if the training data hasn't changed + n_training_steps = 0 if self.hypers[i] else self.n_training_steps + + # Create new candidates + X_cand[i, :, :], y_cand[i, :, :], self.hypers[i] = self._create_candidates( + X, fX, length=self.length[i], n_training_steps=n_training_steps, hypers=self.hypers[i] + ) + self.mode_fit_time = time.process_time() - start + start = time.process_time() + # Select the next candidates + X_next, idx_next = self._select_candidates(X_cand, y_cand) + assert X_next.min() >= 0.0 and X_next.max() <= 1.0 + self.acq_opt_time = time.process_time() - start + # Undo the warping + X_next = from_unit_cube(X_next, self.lb, self.ub) + + # Evaluate batch + fX_next = np.array([[self.f(x)] for x in X_next]) + + # Update trust regions + for i in range(self.n_trust_regions): + idx_i = np.where(idx_next == i)[0] + if len(idx_i) > 0: + self.hypers[i] = {} # Remove model hypers + fX_i = fX_next[idx_i] + + if self.verbose and fX_i.min() < self.fX.min() - 1e-3 * math.fabs(self.fX.min()): + n_evals, fbest = self.n_evals, fX_i.min() + print(f"{n_evals}) New best @ TR-{i}: {fbest:.4}") + sys.stdout.flush() + self._adjust_length(fX_i, i) + + # Update budget and append data + self.n_evals += self.batch_size + self.X = np.vstack((self.X, deepcopy(X_next))) + self.fX = np.vstack((self.fX, deepcopy(fX_next))) + self._idx = np.vstack((self._idx, deepcopy(idx_next))) + + # Check if any TR needs to be restarted + for i in range(self.n_trust_regions): + if self.length[i] < self.length_min: # Restart trust region if converged + idx_i = self._idx[:, 0] == i + + if self.verbose: + n_evals, fbest = self.n_evals, self.fX[idx_i, 0].min() + print(f"{n_evals}) TR-{i} converged to: : {fbest:.4}") + sys.stdout.flush() + + # Reset length and counters, remove old data from trust region + self.length[i] = self.length_init + self.succcount[i] = 0 + self.failcount[i] = 0 + self._idx[idx_i, 0] = -1 # Remove points from trust region + self.hypers[i] = {} # Remove model hypers + + # Create a new initial design + X_init = latin_hypercube(self.n_init, self.dim) + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Print progress + if self.verbose: + n_evals, fbest = self.n_evals, fX_init.min() + print(f"{n_evals}) TR-{i} is restarting from: : {fbest:.4}") + sys.stdout.flush() + + # Append data to local history + self.X = np.vstack((self.X, X_init)) + self.fX = np.vstack((self.fX, fX_init)) + self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) + self.n_evals += self.n_init self.cum_iteration_time = time.process_time() \ No newline at end of file diff --git a/mylib/lib_turbom/turbo/utils.py b/mylib/lib_turbom/turbo/utils.py index 0806812..d9b8837 100644 --- a/mylib/lib_turbom/turbo/utils.py +++ b/mylib/lib_turbom/turbo/utils.py @@ -1,39 +1,39 @@ -############################################################################### -# Copyright (c) 2019 Uber Technologies, Inc. # -# # -# Licensed under the Uber Non-Commercial License (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at the root directory of this project. # -# # -# See the License for the specific language governing permissions and # -# limitations under the License. # -############################################################################### - -import numpy as np - - -def to_unit_cube(x, lb, ub): - """Project to [0, 1]^d from hypercube with bounds lb and ub""" - assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 - xx = (x - lb) / (ub - lb) - return xx - - -def from_unit_cube(x, lb, ub): - """Project from [0, 1]^d to hypercube with bounds lb and ub""" - assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 - xx = x * (ub - lb) + lb - return xx - - -def latin_hypercube(n_pts, dim): - """Basic Latin hypercube implementation with center perturbation.""" - X = np.zeros((n_pts, dim)) - centers = (1.0 + 2.0 * np.arange(0.0, n_pts)) / float(2 * n_pts) - for i in range(dim): # Shuffle the center locataions for each dimension. - X[:, i] = centers[np.random.permutation(n_pts)] - - # Add some perturbations within each box - pert = np.random.uniform(-1.0, 1.0, (n_pts, dim)) / float(2 * n_pts) - X += pert - return X +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import numpy as np + + +def to_unit_cube(x, lb, ub): + """Project to [0, 1]^d from hypercube with bounds lb and ub""" + assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 + xx = (x - lb) / (ub - lb) + return xx + + +def from_unit_cube(x, lb, ub): + """Project from [0, 1]^d to hypercube with bounds lb and ub""" + assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 + xx = x * (ub - lb) + lb + return xx + + +def latin_hypercube(n_pts, dim): + """Basic Latin hypercube implementation with center perturbation.""" + X = np.zeros((n_pts, dim)) + centers = (1.0 + 2.0 * np.arange(0.0, n_pts)) / float(2 * n_pts) + for i in range(dim): # Shuffle the center locataions for each dimension. + X[:, i] = centers[np.random.permutation(n_pts)] + + # Add some perturbations within each box + pert = np.random.uniform(-1.0, 1.0, (n_pts, dim)) / float(2 * n_pts) + X += pert + return X diff --git a/requirements2.txt b/requirements2.txt new file mode 100644 index 0000000..38c12df --- /dev/null +++ b/requirements2.txt @@ -0,0 +1,165 @@ +about-time==4.2.1 +absl-py==1.0.0 +alembic==1.14.0 +alive-progress==3.2.0 +aniso8601==9.0.1 +attrs==21.4.0 +autograd==1.7.0 +ax-platform<=0.4.3 +azure-common==1.1.28 +azure-nspkg==3.0.2 +azure-storage==0.32.0 +blinker==1.9.0 +botorch>=0.6, <=0.9 +catboost==1.2.7 +certifi==2021.10.8 +charset-normalizer==2.0.12 +click==8.1.7 +cloudpickle==3.1.0 +cma==3.2.2 +coco-experiment==2.6.100 +cocopp==2.6.4 +coloredlogs==15.0.1 +ConfigSpace==1.2.1 +contourpy==1.3.1 +cycler==0.11.0 +Cython==3.0.11 +dask==2024.11.2 +dask-jobqueue==0.9.0 +decorator==5.1.1 +Deprecated==1.2.15 +dill==0.3.4 +disjoint_set==0.8.0 +distributed==2024.11.2 +docker==7.1.0 +emcee==3.1.6 +entrypoints==0.4 +filelock==3.16.1 +Flask==3.1.0 +flatbuffers==2.0 +fonttools==4.33.3 +fsspec==2024.10.0 +gitdb==4.0.11 +GitPython==3.1.43 +gpytorch>=1.6,<=1.8.1 +grapheme==0.6.0 +graphene==3.4.3 +graphql-core==3.2.5 +graphql-relay==3.2.0 +graphviz==0.20.3 +greenlet==3.1.1 +gunicorn==21.2.0 +h5py==3.12.1 +HEBO==0.3.6 +humanfriendly==10.0 +idna==3.3 +importlib_metadata==7.2.1 +iniconfig==1.1.1 +ioh==0.3.18 +ipywidgets==8.1.5 +isodate==0.6.1 +itsdangerous==2.2.0 +jax==0.4.35 +jaxlib==0.4.35 +jaxtyping==0.2.19 +Jinja2==3.1.4 +joblib==1.4.2 +jupyterlab_widgets==3.0.13 +kiwisolver==1.4.2 +linear-operator==0.5.3 +locket==1.0.0 +Mako==1.3.6 +Markdown==3.7 +MarkupSafe==3.0.2 +matplotlib==3.9.2 +mkl-service==2.4.0 +ml_dtypes==0.5.0 +mlflow==2.11.3 +more-itertools==10.5.0 +mpmath==1.3.0 +msgpack==1.1.0 +msrest==0.6.21 +multipledispatch==0.6.0 +mypy-extensions==1.0.0 +networkx==3.4.2 +numpy==1.24.4 +numpyro==0.15.3 +nvidia-cublas-cu12==12.4.5.8 +nvidia-cuda-cupti-cu12==12.4.127 +nvidia-cuda-nvrtc-cu12==12.4.127 +nvidia-cuda-runtime-cu12==12.4.127 +nvidia-cudnn-cu12==9.1.0.70 +nvidia-cufft-cu12==11.2.1.3 +nvidia-curand-cu12==10.3.5.147 +nvidia-cusolver-cu12==11.6.1.9 +nvidia-cusparse-cu12==12.3.1.170 +nvidia-nccl-cu12==2.21.5 +nvidia-nvjitlink-cu12==12.4.127 +nvidia-nvtx-cu12==12.4.127 +oauthlib==3.2.2 +opt-einsum==3.3.0 +packaging==23.2 +pandas==2.2.3 +parameterized==0.9.0 +paramz==0.9.6 +partd==1.4.2 +pillow==11.0.0 +plotly==5.24.1 +pluggy==1.0.0 +protobuf==4.25.5 +psutil==6.1.0 +py==1.11.0 +py-expression-eval==0.3.14 +pyaml==24.9.0 +pyarrow==15.0.2 +pyDOE==0.3.8 +pymoo==0.6.0 +pynisher==1.0.10 +pyparsing==3.0.8 +pyre-extensions==0.0.31 +pyrfr==0.9.0 +pyro-api==0.1.2 +pyro-ppl==1.9.1 +pytest==7.1.2 +python-dateutil==2.8.2 +pytz==2024.2 +PyYAML==6.0 +querystring-parser==1.2.4 +regex==2024.11.6 +requests==2.27.1 +requests-oauthlib==1.3.1 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.12 +scikit-learn==1.5.2 +scikit-optimize==0.10.2 +scipy==1.12.0 +seaborn==0.13.2 +six==1.16.0 +smac==2.2.0 +smmap==5.0.1 +sobol-seq==0.2.0 +sortedcontainers==2.4.0 +SQLAlchemy==2.0.36 +sqlparse==0.5.2 +sympy==1.13.1 +tabulate==0.8.9 +tblib==3.0.0 +tenacity==9.0.0 +threadpoolctl==3.1.0 +tomli==2.0.1 +toolz==1.0.0 +torch==2.5.1 +torchvision==0.20.1 +tornado==6.4.2 +tqdm==4.64.0 +triton==3.1.0 +typeguard==4.4.1 +typing-inspect==0.9.0 +typing_extensions==4.12.2 +tzdata==2024.2 +urllib3==1.26.9 +Werkzeug==3.1.3 +widgetsnbextension==4.0.13 +wrapt==1.17.0 +zict==3.0.0 +zipp==3.8.0 diff --git a/run_experiment.py b/run_experiment.py index 5f3c71e..e5aefda 100644 --- a/run_experiment.py +++ b/run_experiment.py @@ -1,114 +1,233 @@ -from functools import partial -import random -from wrapper import wrapopt -from my_logger import MyIOHFormatOnEveryEvaluationLogger, MyObjectiveFunctionWrapper -import sys -import os -import json -from ioh import Experiment, get_problem, logger, problem, OptimizationType -import numpy as np -import copy -import time -from datetime import timedelta - - -def decide_doe_size(dim): - return dim - - -def decide_total_budget(dim, doe_size): - return 10 * dim + 50 - - -class AlgorithmWrapper: - def __init__(self, seed): - self.opt = None - self.seed = seed - - @staticmethod - def __fitness_function_wrapper(x, f): - if type(x) is np.ndarray: - x = x.tolist() - return f(x) - - @staticmethod - def create_fitness(my_function): - return partial(AlgorithmWrapper.__fitness_function_wrapper, f=my_function) - - def __call__(self, optimizer_name, f, fid, iid, dim): - self.dim = dim - self.optimizer_name = optimizer_name - func = partial(AlgorithmWrapper.__fitness_function_wrapper, f=f) - doe_size = decide_doe_size(self.dim) - total_budget = decide_total_budget(self.dim, doe_size) - self.opt = wrapopt( - optimizer_name, func, self.dim, total_budget, doe_size, self.seed) - self.opt.run() - - - # @property - # def lower_space_dim(self) -> int: - # if self.optimizer_name == 'BO': - # return self.dim - # return self.opt.get_lower_space_dimensionality() - # - # @property - # def extracted_information(self) -> float: - # if self.optimizer_name == 'BO': - # return 1.0 - # return self.opt.get_extracted_information() - # - # @property - # def kernel_config(self) -> str: - # return self.opt._pca.get_kernel_parameters() - # - # @property - # def out_of_the_box_solutions(self) -> int: - # return self.opt.out_solutions - - @property - def acq_opt_time(self) -> float: - return self.opt.get_acq_time() - - @property - def model_fit_time(self) -> float: - return self.opt.get_mode_time() - - @property - def cum_iteration_time(self) -> float: - return self.opt.get_iter_time() - -def run_particular_experiment(my_optimizer_name, fid, iid, dim, rep, folder_name): - algorithm = AlgorithmWrapper(rep) - l = MyIOHFormatOnEveryEvaluationLogger( - folder_name=folder_name, algorithm_name=my_optimizer_name) - print(f' Logging to the folder {l.folder_name}') - sys.stdout.flush() - # l.watch(algorithm, []) - l.watch(algorithm, ['acq_opt_time', 'model_fit_time', 'cum_iteration_time']) - p = MyObjectiveFunctionWrapper(fid, iid, dim) - p.attach_logger(l) - print("dim = ", dim) - algorithm(my_optimizer_name, p, fid, iid, dim) - l.finish_logging() - - -def run_experiment(): - if len(sys.argv) == 1: - print('No configs given') - return - with open(sys.argv[1]) as f: - m = json.load(f) - print(f'Running with config {m} ...') - start = time.process_time() - run_particular_experiment( - m['opt'], m['fid'], m['iid'], m['dim'], m['seed'], m['folder']) - end = time.process_time() - sec = int(round(end - start)) - x = str(timedelta(seconds=sec)).split(':') - print( - f' Done in {sec} seconds. Which is {x[0]} hours, {x[1]} minutes and {x[2]} seconds') - - -if __name__ == '__main__': - run_experiment() +from functools import partial +import random +from wrapper import wrapopt, Abstract_Optimizer_Wrapper +from my_logger import MyIOHFormatOnEveryEvaluationLogger2 +import sys +import os +import json + + +from typing import Callable + +import ioh + +import numpy as np +from numpy import ndarray +import copy +import time +from typing import List, Tuple, Optional, Union +#from warnings import deprecated +from datetime import timedelta + + +def decide_doe_size(dim): + return dim + + +def decide_total_budget(dim, doe_size): + return min(100 * dim , 1000) + +### ============================================ +### CONSTANTS +### ============================================ +TRIGGER = ioh.logger.trigger.Each(10) +ONIMPROVEMENT = ioh.logger.trigger.ON_IMPROVEMENT +ALWAYS = ioh.logger.trigger.ALWAYS + +### DEPRECATED CLASS ### +class AlgorithmWrapper: + def __init__(self, + seed:int): + self.opt = None + self.seed = seed + + + def __call__(self, + optimizer_name:str, + f:ioh.problem.RealSingleObjective, + fid, iid, dim, sample_zero): + self.dim = dim + self.optimizer_name = optimizer_name + doe_size = decide_doe_size(self.dim) + total_budget = decide_total_budget(self.dim, doe_size) + self.opt = wrapopt( + optimizer_name, f, self.dim, total_budget, doe_size, self.seed, sample_zero) + self.opt.run() + + + # @property + # def lower_space_dim(self) -> int: + # if self.optimizer_name == 'BO': + # return self.dim + # return self.opt.get_lower_space_dimensionality() + # + # @property + # def extracted_information(self) -> float: + # if self.optimizer_name == 'BO': + # return 1.0 + # return self.opt.get_extracted_information() + # + # @property + # def kernel_config(self) -> str: + # return self.opt._pca.get_kernel_parameters() + # + # @property + # def out_of_the_box_solutions(self) -> int: + # return self.opt.out_solutions + + @property + def acq_opt_time(self) -> float: + return self.opt.get_acq_time() + + @property + def model_fit_time(self) -> float: + return self.opt.get_mode_time() + + @property + def cum_iteration_time(self) -> float: + return self.opt.get_iter_time() + + + +def logger_func(folder_name,my_optimizer_name,sample_zero:bool,**kwargs)->ioh.logger.Analyzer: + # Set up the logger + triggers = [ONIMPROVEMENT, + TRIGGER] + + alg_info:str = "" + + if sample_zero: + alg_info = "with sampling zero vector" + + l = ioh.logger.Analyzer(triggers=triggers, + additional_properties=[ioh.logger.property.RAWYBEST], + root=os.getcwd(), + folder_name=folder_name, + algorithm_name=my_optimizer_name, + algorithm_info=alg_info, + store_positions=False) + + return l + +def logger_func_2(folder_name,my_optimizer_name,sample_zero:bool,**kwargs)->ioh.logger.Analyzer: + # Set up the logger + triggers = [ALWAYS] + + alg_info:str = "" + + if sample_zero: + alg_info = "with sampling zero vector" + + l = MyIOHFormatOnEveryEvaluationLogger2(triggers=triggers, + properties=[], + root=os.getcwd(), + folder_name=folder_name, + algorithm_name=my_optimizer_name, + algorithm_info=alg_info) + + return l + +def run_particular_experiment(**kwargs): + + + my_optimizer_name = str(kwargs.pop('opt',"BO_botorch")).strip() + fid = int(kwargs.pop('fid',1)) # Run the sphere function as default + iid = int(kwargs.pop('iid',0)) # Run the instance 0 as default + dim = int(kwargs.pop('dim',5)) # Run dimension 5 as a default + seed = int(kwargs.pop('seed',43)) # Use seed 43 as a default + rep = int(kwargs.pop('rep',1)) # Set a default of 1 repetition of the experiment + folder_name = str(kwargs.pop('folder')).strip() + logger_type = str(kwargs.pop('logger','simple')).lower().strip() + sample_zero = bool(kwargs.pop('sample_zero',False)) + + # Perform some checks + if len(folder_name) == 0 or folder_name=="": + raise ValueError("The folder name is empty!") + + + p:ioh.problem.RealSingleObjective = ioh.get_problem(fid=fid, + instance=iid, + dimension=dim, + problem_class=ioh.ProblemClass.BBOB) + + algorithm:Abstract_Optimizer_Wrapper = wrapopt(optimizer_name=my_optimizer_name, + func=p, + ml_dim=dim, + ml_total_budget=decide_total_budget(dim,decide_doe_size(dim)), + ml_DoE_size=decide_doe_size(dim), + random_seed=seed, + sample_zero=sample_zero + ) + print("dim = ", dim) + + if logger_type == "complete": + l = logger_func_2( + folder_name=folder_name, my_optimizer_name=my_optimizer_name, + sample_zero=sample_zero) + #print(f' Logging to the folder {l.folder_name}') + #sys.stdout.flush() + l.watch(algorithm, ['acq_opt_time', 'model_fit_time', 'cum_iteration_time']) + # try: + # l.watch(algorithm,'loss') + # except Exception as e: + # print(e.args) + #l.watch(p,["loss","best_loss"]) + p.attach_logger(l) + + elif logger_type == "simple": + # Use the default logger from IOH + + + # Set up the logger + l = logger_func(folder_name=folder_name, + my_optimizer_name=my_optimizer_name, + sample_zero=sample_zero) + + p.attach_logger(l) + else: + raise AttributeError("The logger type is not either set to 'simple' or 'complete'!", + name="logger_type", + obj=logger_type) + + + try: + algorithm.run() + + except KeyboardInterrupt as e: + print("---Keyboard Cancellation---", + e.args) + if isinstance(l,MyIOHFormatOnEveryEvaluationLogger2): + l.finish_logging() + except ValueError as e: + print("---Value error---", + e.args) + if isinstance(l,MyIOHFormatOnEveryEvaluationLogger2): + l.finish_logging() + + except Exception as e: + print("---Unexpected error---", + e.args) + if isinstance(l,MyIOHFormatOnEveryEvaluationLogger2): + l.finish_logging() + else: + p.detach_logger() + + +def run_experiment(): + if len(sys.argv) == 1: + print('No configs given') + return + with open(sys.argv[1]) as f: + m = json.load(f) + print(f'Running with config {m} ...') + start = time.process_time() + run_particular_experiment(**m) + end = time.process_time() + sec = int(round(end - start)) + x = str(timedelta(seconds=sec)).split(':') + print( + f' Done in {sec} seconds. Which is {x[0]} hours, {x[1]} minutes and {x[2]} seconds') + + +if __name__ == '__main__': + run_experiment() diff --git a/total_config.json b/total_config.json index 40479f4..8b321b1 100644 --- a/total_config.json +++ b/total_config.json @@ -1,11 +1,14 @@ -{ - "folder": "test", - "optimizers": ["turbo1", "turbom"], - "fids": [1, 2], - "iids": [0, 1], - "dims": [10, 20], - "reps": 2, - "lb": -5, - "ub": 5, - "extra": "Tests" -} +{ + "folder": "Benchmark_linearPCABO_medium_correct", + "optimizers": ["linearPCABO","turbom"], + "fids": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24], + "iids": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14], + "dims": [20,40], + "seed": [43], + "reps": 1, + "lb": -5, + "ub": 5, + "logger":"simple", + "sample_zero":0, + "extra": "Trial_linearPCABO_medium_correct" +} diff --git a/wrapper.py b/wrapper.py index b4bd1d5..83126c6 100644 --- a/wrapper.py +++ b/wrapper.py @@ -1,967 +1,2265 @@ -import time - -import numpy as np -import sys -import os -from ioh import get_problem - -class Py_CMA_ES_Wrapper: - def __init__(self, func, dim, ub, lb, total_budget, random_seed): - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - bayes_bo_lib = os.path.join( - my_dir, 'mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization') - self.func = func - self.dim = dim - self.total_budget = total_budget - self.random_seed = random_seed - self.ub = ub - self.lb = lb - - def run(self): - from bayes_optim import RandomForest, BO, GaussianProcess - import cma - from bayes_optim.extension import RealSpace - - import random - space = RealSpace([self.lb, self.ub], random_seed=self.random_seed) * self.dim - ma = float('-inf') - argmax = None - for i in range(10*self.dim): - x = space.sample(1)[0] - def generate_initial_point(): - return np.random.uniform(low=self.lb, high=self.ub, size = self.dim) - - cma.fmin2(self.func, generate_initial_point, 1., restarts=5,options={'bounds': [ - - [self.lb]*self.dim, [self.ub]*self.dim], 'maxfevals': self.total_budget, 'seed': self.random_seed, 'tolx': 1e-8}) - - - - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - -class SaasboWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - sys.path.append(os.path.join(my_dir, 'mylib', 'lib_saasbo')) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - #from saasbo import run_saasbo, get_acq_time, get_mode_time - from saasbo import Saasbo - - - # run_saasbo( - # self.func, - # np.ones(self.dim) * self.ub, - # np.ones(self.dim) * self.lb, - # self.total_budget, - # self.Doe_size, - # self.random_seed, - # alpha=0.01, - # num_warmup=256, - # num_samples=256, - # thinning=32, - # device="cpu", - # ) - - self.opt = Saasbo(func=self.func, - dim=self.dim, - ub=self.ub, - lb=self.lb, - total_budget=self.total_budget, - DoE_size=self.Doe_size, - random_seed=self.random_seed) - - print(self.opt.run_saasbo(self.func,np.ones(self.dim) * self.ub,np.ones(self.dim) * self.lb,self.total_budget,self.Doe_size,self.random_seed,alpha=0.01,num_warmup=256,num_samples=256,thinning=32,device="cpu",)) - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - - - -class BO_sklearnWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - sys.path.append(os.path.join(my_dir, 'mylib', 'lib_BO_sklearn')) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - from bosklearn import bosklearn - - self.opt= bosklearn(func=self.func, - dim=self.dim, - ub=self.ub, - lb=self.lb, - total_budget=self.total_budget, - DoE_size=self.Doe_size, - random_seed=self.random_seed) - self.opt.gp_minimize(self.func, # the function to minimize - # the bounds on each dimension of x - list((((self.lb, self.ub),) * self.dim)), - acq_func="EI", # the acquisition function - n_calls=self.total_budget, # the number of evaluations of f - n_random_starts=self.Doe_size, # the number of random initialization points - noise=0.1 ** 2, # the noise level (optional) - random_state=self.random_seed) - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - -class BO_bayesoptimWrapper: - # BO of Hao - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - sys.path.append('./mylib/' + 'lib_' + "BO_bayesoptim") - print(sys.path) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - from bayes_optim import BO, RealSpace - from bayes_optim.surrogate import GaussianProcess - - space = RealSpace([self.lb, self.ub]) * \ - self.dim # create the search space - - # hyperparameters of the GPR model - thetaL = 1e-10 * (self.ub - self.lb) * np.ones(self.dim) - thetaU = 10 * (self.ub - self.lb) * np.ones(self.dim) - model = GaussianProcess( # create the GPR model - thetaL=thetaL, thetaU=thetaU - ) - - opt = BO( - search_space=space, - obj_fun=self.func, - model=model, - DoE_size=self.Doe_size, # number of initial sample points - max_FEs=self.total_budget, # maximal function evaluation - verbose=True - ) - opt.run() - - -class BO_development_bayesoptimWrapper: - # Latest changes from Hao's repository - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - bayes_bo_lib = os.path.join( - my_dir, 'mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization') - if not os.path.isdir(bayes_bo_lib): - raise ImportError( - 'No such module Bayesian-Optimization, please consider cloning this repository: https://github.com/wangronin/Bayesian-Optimization to the folder mylib/lib_BO_bayesoptim/') - sys.path.insert(0, bayes_bo_lib) - print(sys.path) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - from bayes_optim.extension import RealSpace - from bayes_optim.bayes_opt import BO - - space = RealSpace([self.lb, self.ub], random_seed=self.random_seed) * self.dim - opt = BO( - search_space=space, - obj_fun=self.func, - DoE_size=self.Doe_size, - n_point=1, - random_seed=self.random_seed, - acquisition_optimization={"optimizer": "BFGS"}, - max_FEs=self.total_budget, - verbose=False, - ) - opt.run() - -class KPCABOWrapper: - # Latest changes from Hao's repository - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - bayes_bo_lib = os.path.join( - my_dir, 'mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization') - if not os.path.isdir(bayes_bo_lib): - raise ImportError( - 'No such module Bayesian-Optimization, please unzip the folder Bayesian-Optimization.zip and move the unzip folder to mylib/lib_BO_bayesoptim/') - sys.path.insert(0, bayes_bo_lib) - print(sys.path) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - - - from bayes_optim import RandomForest, BO, GaussianProcess - - from bayes_optim.extension import PCABO, RealSpace, KernelPCABO, KernelFitStrategy - from bayes_optim.mylogging import eprintf - - import random - - - space = RealSpace([self.lb, self.ub], random_seed=self.random_seed) * self.dim - self.opt = KernelPCABO( - search_space=space, - obj_fun=self.func, - DoE_size=self.Doe_size, - max_FEs=self.total_budget, - verbose=False, - n_point=1, - acquisition_optimization={"optimizer": "BFGS"}, - max_information_loss=0.1, - kernel_fit_strategy=KernelFitStrategy.AUTO, - NN=self.dim, - random_seed=self.random_seed - ) - - print(self.opt.run()) - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - - -class randomWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - import ioh - - def random_search(func, search_space, budget): - """ - Implementa la ricerca casuale per minimizzare un problema di dimensione n. - - Parameters: - - objective_function: La funzione obiettivo da minimizzare. Deve accettare un vettore di dimensione n come input e restituire un valore numerico. - - search_space: Una lista di tuple, ognuna contenente il range di valori ammissibili per ciascuna dimensione. - - budget: Il numero massimo di valutazioni della funzione obiettivo consentite. - - Returns: - - best_solution: La migliore soluzione trovata. - - best_score: Il valore minimo della funzione obiettivo associato alla migliore soluzione. - """ - best_solution = None - best_score = float('inf') - - for _ in range(budget): - solution = [np.random.uniform(low, high) for (low, high) in search_space] - score = self.func(solution) - - # Aggiorna la migliore soluzione se necessario - if score < best_score: - best_solution = solution - best_score = score - print(best_score) - return best_solution, best_score - search_space = [(self.lb, self.ub) for _ in range(self.dim)] - budget = self.total_budget - random_search(self.func, search_space, budget) - - -class linearPCABOWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - #import sys - #sys.path.append('./mylib/' + 'lib_' + "linearPCABO") - #print(sys.path) - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - bayes_bo_lib = os.path.join( - my_dir, 'mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization') - if not os.path.isdir(bayes_bo_lib): - raise ImportError( - 'No such module Bayesian-Optimization, please unzip the folder Bayesian-Optimization.zip and move the unzip folder to mylib/lib_BO_bayesoptim/') - sys.path.insert(0, bayes_bo_lib) - print(sys.path) - #sys.path.insert(0, bayes_bo_lib) - #print(sys.path) - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - #import sys - #sys.path.insert(0, "./mylib/lib_linearPCABO/Bayesian-Optimization") - from bayes_optim.extension import PCABO, RealSpace - - space = RealSpace([self.lb, self.ub]) * self.dim - self.opt = PCABO( - search_space=space, - obj_fun=self.func, - DoE_size=self.Doe_size, - max_FEs=self.total_budget, - verbose=False, - n_point=1, - n_components=0.90, - acquisition_optimization={"optimizer": "BFGS"}, - random_seed=self.random_seed - ) - - print(self.opt.run()) - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - - -class RDUCBWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - # import sys - # sys.path.append('./mylib/' + 'lib_' + "linearPCABO") - # print(sys.path) - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - sys.path.append(os.path.join(my_dir, 'mylib', 'lib_RDUCB/HEBO/RDUCB')) - print(sys.path) - # sys.path.insert(0, bayes_bo_lib) - # print(sys.path) - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - # import sys - # sys.path.insert(0, "./mylib/lib_linearPCABO/Bayesian-Optimization") - - from hdbo.algorithms import RDUCB - self.opt = RDUCB( algorithm_random_seed=self.random_seed, - eps=-1, - exploration_weight= 'lambda t: 0.5 * np.log(2*t)', - graphSamplingNumIter=100, - learnDependencyStructureRate=1, - lengthscaleNumIter=2, - max_eval=-4, - noise_var= 0.1, - param_n_iter=16, - size_of_random_graph=0.2, - # data_random_seed=self.random_seed, - fn_noise_var=0.15, - grid_size=150, - fn= self.func, -n_iter=self.total_budget-self.Doe_size, -n_rand=self.Doe_size, dim=self.dim,) - self.opt.run() - - def get_acq_time(self): - return self.opt.mybo.acq_opt_time - - def get_mode_time(self): - return self.opt.mybo.mode_fit_time - - def get_iter_time(self): - return self.opt.mybo.cum_iteration_time - - -class turbo1Wrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - #import sys - #sys.path.append('./mylib/' + 'lib_' + "turbo1") - #print(sys.path) - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - sys.path.append(os.path.join(my_dir, 'mylib', 'lib_turbo1')) - print(sys.path) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - from turbo import Turbo1 - import torch - import math - import matplotlib - import matplotlib.pyplot as plt - self.opt = Turbo1( - f=self.func, # Handle to objective function - lb=np.ones(self.dim) * self.lb, # Numpy array specifying lower bounds - ub=np.ones(self.dim) * self.ub, # Numpy array specifying upper bounds - n_init=self.Doe_size, # Number of initial bounds from an Latin hypercube design - max_evals=self.total_budget, # Maximum number of evaluations - batch_size=5, # How large batch size TuRBO uses - verbose=True, # Print information from each batch - use_ard=True, # Set to true if you want to use ARD for the GP kernel - max_cholesky_size=2000, # When we switch from Cholesky to Lanczos - n_training_steps=50, # Number of steps of ADAM to learn the hypers - min_cuda=1024, # Run on the CPU for small datasets - device="cpu", # "cpu" or "cuda" - dtype="float64", # float64 or float32 - ) - self.opt.optimize() - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - - -class turbomWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - #import sys - #sys.path.append('./mylib/' + 'lib_' + "turbom") - #print(sys.path) - - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - sys.path.append(os.path.join(my_dir, 'mylib', 'lib_turbo1')) - print(sys.path) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - from turbo import TurboM - import torch - import math - import matplotlib - import matplotlib.pyplot as plt - #tr = math.floor(self.total_budget / self.Doe_size) - 1 - tr = int(self.dim/5) - n_init = math.floor(self.Doe_size/tr) - self.opt = TurboM( - f=self.func, # Handle to objective function - lb=np.ones(self.dim) * self.lb, # Numpy array specifying lower bounds - ub=np.ones(self.dim) * self.ub, # Numpy array specifying upper bounds - n_init=n_init, # Number of initial bounds from an Symmetric Latin hypercube design - max_evals=self.total_budget, # Maximum number of evaluations - n_trust_regions=tr, # Number of trust regions - batch_size=5, # How large batch size TuRBO uses - verbose=True, # Print information from each batch - use_ard=True, # Set to true if you want to use ARD for the GP kernel - max_cholesky_size=2000, # When we switch from Cholesky to Lanczos - n_training_steps=50, # Number of steps of ADAM to learn the hypers - min_cuda=1024, # Run on the CPU for small datasets - device="cpu", # "cpu" or "cuda" - dtype="float64", # float64 or float32 - ) - self.opt.optimize() - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - -class EBOWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - # import sys - # sys.path.append('./mylib/' + 'lib_' + "EBO") - # print(sys.path) - - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - sys.path.append(os.path.join(my_dir, 'mylib', 'lib_EBO')) - print(sys.path) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - import numpy.matlib as nm - import functools - from ebo_core.ebo import ebo - from test_functions.simple_functions import sample_z - import time - import logging - - dx = self.dim - z = sample_z(dx) - k = np.array([10] * dx) - x_range = nm.repmat([[self.lb], [self.ub]], 1, self.dim) - x_range = x_range.astype(float) - sigma = 0.01 - n = self.Doe_size - budget = self.total_budget - f = self.func - f = functools.partial(lambda f, x: -f(x), f) - options = {'x_range': x_range, # input domain - 'dx': x_range.shape[1], # input dimension - 'max_value': 0, # target value - 'T': budget, # number of iterations - 'B': 1, # number of candidates to be evaluated - 'dim_limit': 3, # max dimension of the input for each additive function component - 'isplot': 0, # 1 if plotting the result; otherwise 0. - 'z': None, 'k': None, # group assignment and number of cuts in the Gibbs sampling subroutine - 'alpha': 1., # hyperparameter of the Gibbs sampling subroutine - 'beta': np.array([5., 2.]), - 'opt_n': 1000, # points randomly sampled to start continuous optimization of acfun - 'pid': 'test3', # process ID for Azure - 'datadir': 'tmp_data/', # temporary data directory for Azure - 'gibbs_iter': 10, # number of iterations for the Gibbs sampling subroutine - 'useAzure': False, # set to True if use Azure for batch evaluation - 'func_cheap': True, # if func cheap, we do not use Azure to test functions - 'n_add': None, # this should always be None. it makes dim_limit complicated if not None. - 'nlayers': 100, # number of the layers of tiles - 'gp_type': 'l1', # other choices are l1, sk, sf, dk, df - 'gp_sigma': 0.1, # noise standard deviation - 'n_bo': 10, # min number of points selected for each partition - 'n_bo_top_percent': 0.5, # percentage of top in bo selections - 'n_top': 10, # how many points to look ahead when doing choose Xnew - 'min_leaf_size': 10, # min number of samples in each leaf - 'max_n_leaves': 10, # max number of leaves - 'thresAzure': 1, # if batch size > thresAzure, we use Azure - 'save_file_name': 'tmp/tmp.pk', - } - self.opt = ebo(f, options) - start = time.time() - self.opt.run() - - print("elapsed time: ", time.time() - start) - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - -class EBO_BWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - # import sys - # sys.path.append('./mylib/' + 'lib_' + "EBO") - # print(sys.path) - - import pathlib - my_dir = pathlib.Path(__file__).parent.resolve() - sys.path.append(os.path.join(my_dir, 'mylib', 'lib_EBO')) - print(sys.path) - - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - import numpy.matlib as nm - import functools - from ebo_core.ebo import ebo - from test_functions.simple_functions import sample_z - import time - import logging - - dx = self.dim - z = sample_z(dx) - k = np.array([10] * dx) - x_range = nm.repmat([[self.lb], [self.ub]], 1, self.dim) - x_range = x_range.astype(float) - sigma = 0.01 - n = self.Doe_size - budget = self.total_budget - t= int (float(budget)/10) - f = self.func - f = functools.partial(lambda f, x: -f(x), f) - options = {'x_range': x_range, # input domain - 'dx': x_range.shape[1], # input dimension - 'max_value': 0, # target value - 'T': t, # number of iterations - 'B': 10, # number of candidates to be evaluated - 'dim_limit': 3, # max dimension of the input for each additive function component - 'isplot': 0, # 1 if plotting the result; otherwise 0. - 'z': None, 'k': None, # group assignment and number of cuts in the Gibbs sampling subroutine - 'alpha': 1., # hyperparameter of the Gibbs sampling subroutine - 'beta': np.array([5., 2.]), - 'opt_n': 1000, # points randomly sampled to start continuous optimization of acfun - 'pid': 'test3', # process ID for Azure - 'datadir': 'tmp_data/', # temporary data directory for Azure - 'gibbs_iter': 10, # number of iterations for the Gibbs sampling subroutine - 'useAzure': False, # set to True if use Azure for batch evaluation - 'func_cheap': True, # if func cheap, we do not use Azure to test functions - 'n_add': None, # this should always be None. it makes dim_limit complicated if not None. - 'nlayers': 100, # number of the layers of tiles - 'gp_type': 'l1', # other choices are l1, sk, sf, dk, df - 'gp_sigma': 0.1, # noise standard deviation - 'n_bo': 10, # min number of points selected for each partition - 'n_bo_top_percent': 0.5, # percentage of top in bo selections - 'n_top': 10, # how many points to look ahead when doing choose Xnew - 'min_leaf_size': 10, # min number of samples in each leaf - 'max_n_leaves': 10, # max number of leaves - 'thresAzure': 1, # if batch size > thresAzure, we use Azure - 'save_file_name': 'tmp/tmp.pk', - } - self.opt = ebo(f, options) - start = time.time() - self.opt.run() - - print("elapsed time: ", time.time() - start) - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - -class ALEBOWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - # import sys - # sys.path.append('./mylib/' + 'lib_' + "linearPCABO") - # print(sys.path) - - # from ax.modelbridge.strategies.alebo import ALEBOStrategy - # sys.path.insert(0, bayes_bo_lib) - # print(sys.path) - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - self.iter = 0 - - def run(self): - # import pathlib - # my_dir = pathlib.Path(__file__).parent.resolve() - # sys.path.append(os.path.join(my_dir, 'mylib', 'lib_ALEBO')) - # import numpy as np - from ax.utils.measurement.synthetic_functions import branin - print(sys.path) - - def branin_evaluation_function(parameterization): - # Evaluates Branin on the first two parameters of the parameterization. - # Other parameters are unused. - x = np.array([parameterization["x0"], parameterization["x1"]]) - - return {"objective": (branin(x), 0.0)} - def function(parameterization): - # Evaluates Branin on the first two parameters of the parameterization. - # Other parameters are unused. - x = np.array([parameterization[f'x{i}'] for i in range(self.dim)]) - self.iter+=1 - if self.iter == self.total_budget: - print("Optimization is complete, cannot run another trial.") - exit() - return {"objective": (self.func(x), 0.0)} - - parameters = [ - {"name": "x0", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, - {"name": "x1", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, - ] - parameters.extend([ - {"name": f"x{i}", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"} - for i in range(2, self.dim) - ]) - from ax.modelbridge.strategies.alebo import ALEBOStrategy - alebo_strategy = ALEBOStrategy(D=self.dim, d=4, init_size=self.Doe_size) - alebo_strategy._steps[0].model_kwargs.update({"seed": self.random_seed}) - from ax.service.managed_loop import optimize - self.opt = optimize( - parameters=parameters, - experiment_name="test", - objective_name="objective", - evaluation_function=function, - minimize=True, - total_trials=self.total_budget, - generation_strategy=alebo_strategy, - ) - self.opt() -# def run(self): -# # import sys -# # sys.path.insert(0, "./mylib/lib_linearPCABO/Bayesian-Optimization") -# parameters = [ -# {"name": "x0", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, -# {"name": "x1", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, -# ] -# parameters.extend([ -# {"name": f"x{i}", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"} -# for i in range(2, self.dim) -# ]) -# alebo_strategy = ALEBOStrategy(D=self.dim, d=10, init_size=self.Doe_size) -# from ax.service.managed_loop import optimize -# self.opt = optimize( -# parameters=parameters, -# experiment_name="test", -# objective_name="objective", -# evaluation_function=self.func, -# minimize=True, -# total_trials=self.total_budget, -# generation_strategy=alebo_strategy, -# ) -# self.opt() - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - -class HEBOWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - # import sys - # sys.path.append('./mylib/' + 'lib_' + "linearPCABO") - # print(sys.path) - # import pathlib - # my_dir = pathlib.Path(__file__).parent.resolve() - # sys.path.append(os.path.join(my_dir, 'mylib', 'lib_RDUCB/HEBO/RDUCB')) - # print(sys.path) - # sys.path.insert(0, bayes_bo_lib) - # print(sys.path) - self.func = func - self.dim = dim - self.ub = ub - self.lb = lb - self.total_budget = total_budget - self.Doe_size = DoE_size - self.random_seed = random_seed - - def run(self): - # import sys - # sys.path.insert(0, "./mylib/lib_linearPCABO/Bayesian-Optimization") - import pandas as pd - import numpy as np - from hebo.design_space.design_space import DesignSpace - from hebo.optimizers.hebo import HEBO - # def obj(params: pd.DataFrame) -> np.ndarray: - # return ((params.values - 0.37) ** 2).sum(axis=1).reshape(-1, 1) - def obj(params: pd.DataFrame) -> np.ndarray: - # Imposta la funzione BBOB desiderata (ad esempio, la funzione 1) - problem = self.func # Parametri: dimensione, funzione BBOB (1-24) - - # Calcola il valore della funzione obiettivo per ciascuna riga dei parametri - values = [problem(np.squeeze(row.values)) for _, row in params.iterrows()] - - # Restituisci i valori come un array numpy - return np.array(values).reshape(-1, 1) - - dimension_specs = [{"name": f"param{i}", "type": "num", 'lb' : self.lb, 'ub' : self.ub } for i in - range(1, self.dim + 1)] - space = DesignSpace().parse(dimension_specs) - #space = DesignSpace().parse([{'name': 'x', 'type': 'int', 'lb': self.lb, 'ub': self.ub}]) - self.opt = HEBO(space, rand_sample=self.Doe_size, scramble_seed=self.random_seed ) - for i in range(self.total_budget): - rec = self.opt.suggest(n_suggestions=1) - self.opt.observe(rec, obj(rec)) - print('After %d iterations, best obj is %.2f' % (i, self.opt.y.min())) - self.opt.cum_iteration_time = time.process_time() - - def get_acq_time(self): - return self.opt.acq_opt_time - - def get_mode_time(self): - return self.opt.mode_fit_time - - def get_iter_time(self): - return self.opt.cum_iteration_time - -import torch -import os -import sys -from botorch.models import SingleTaskGP -from botorch.fit import fit_gpytorch_mll -from botorch.acquisition import LogExpectedImprovement -from botorch.optim.optimize import optimize_acqf -from botorch.sampling import SobolQMCNormalSampler -from gpytorch.mlls import ExactMarginalLogLikelihood -from botorch.models.transforms import Normalize, Standardize -class BO_botorchWrapper: - def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): - self.func = func - self.dim = dim - self.lb = torch.tensor([lb] * dim, dtype=torch.float32) - self.ub = torch.tensor([ub] * dim, dtype=torch.float32) - bounds = torch.stack([self.lb, self.ub]) - self.total_budget = total_budget - self.DoE_size = DoE_size - self.random_seed = random_seed - torch.manual_seed(random_seed) - def run(self): - self.X = self.lb + (self.ub - self.lb) * torch.rand((self.DoE_size, self.dim)) - results = [[self.func(x.tolist())] for x in self.X] - self.Y = torch.tensor(results, dtype=torch.float32).view(-1, 1) - - for i in range(self.total_budget - self.DoE_size): - self.model = SingleTaskGP(self.X, self.Y, input_transform=Normalize(d=self.dim), - outcome_transform=Standardize(m=1),) - self.mll = ExactMarginalLogLikelihood(self.model.likelihood, self.model) - fit_gpytorch_mll(self.mll) - - EI = LogExpectedImprovement(self.model, best_f=self.Y.max(), maximize=False) - bounds = torch.stack([self.lb, self.ub]).unsqueeze(1) if self.dim == 1 else torch.stack([self.lb, self.ub]) - candidate, _ = optimize_acqf( - EI, - bounds=bounds, - q=1, - num_restarts=5, - raw_samples=20, - ) - - new_y = torch.tensor([[self.func(candidate.tolist()[0])]]) - - self.X = torch.cat([self.X, candidate]) - self.Y = torch.cat([self.Y, new_y]) - - -def wrapopt(optimizer_name, func, ml_dim, ml_total_budget, ml_DoE_size, random_seed): - ub = +5 - lb = -5 - if optimizer_name == "saasbo": - return SaasboWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == "BO_sklearn": - return BO_sklearnWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == "BO_bayesoptim": - return BO_bayesoptimWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == "random": - return randomWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == "linearPCABO": - return linearPCABOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == "turbo1": - return turbo1Wrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == "turbom": - return turbomWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'BO_dev_Hao': - return BO_development_bayesoptimWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'EBO': - return EBOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'EBO_B': - return EBO_BWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'KPCABO': - return KPCABOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'pyCMA': - return Py_CMA_ES_Wrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, - random_seed=random_seed) - - if optimizer_name == 'RDUCB': - return RDUCBWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'ALEBO': - return ALEBOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'HEBO': - return HEBOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - if optimizer_name == 'BO_botorch': - return BO_botorchWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, - random_seed=random_seed) - -if __name__ == "__main__": - dim = 10 - total_budget = 30 - doe_size = dim - seed = 2 - # Algorithm alternatives: - algorithm_name = "turbo1" - f = get_problem(1, dimension=dim, instance=1, problem_type='Real') - - opt = wrapopt(algorithm_name, f, dim, total_budget, doe_size, seed) - opt.run() +import time + +import numpy as np +import sys +import os +from ioh import get_problem +from ioh.iohcpp.problem import RealSingleObjective + +from abc import ABC, abstractmethod + +from copy import copy, deepcopy + +import pathlib + +from typing import Union, List, Tuple, Optional, Callable + +import warnings + +## TODO: ADDENDA TO HANDLE IOH Objects +from ioh.iohcpp.problem import RealSingleObjective + +__set_directory__= os.path.abspath(os.path.dirname(__file__)) +print(__set_directory__) + + + +## TODO: ADDENDA TO GENERATE AN 'ABSTRACT CLASS' FOR THE OTHER METHODS TO INHERIT FROM + +class Abstract_Optimizer_Wrapper(ABC): + r""" + This is an abstract class to avoid further repetition of methods along all the + upcoming wrappers. + """ + + # The library pointing to the library + libpath:Union[pathlib.Path,str]= pathlib.Path("") + + # The base directory (which is the same as this file) + _base_dir:pathlib.Path = pathlib.Path(__file__).parent.resolve() + + def __init__(self, + func:Callable, + dim:int, + ub:np.ndarray, + lb:np.ndarray, + total_budget:Optional[int]=100, + random_seed:Optional[int]=42): + + r""" + This is the class initializer for the optimizer wrapper; + + Args: + ------------------- + - func: `Callable`: A callable object which returns a function evaluation given a set of parameters. + - dim: `int`: The dimension of the optimization problem. + - ub: `np.ndarray`: The general upper bound of the problem. + - lb: `np.ndarray`: The general lower bound of the problem. + - total_budget: `Optional[int]`: The budget of evaluations of the optimizer. Set to 100 by default. + - random_seed: `Optional[int]`: An integer denoting the initial seed of the pseudo-random number generator. By default, it is set to 42. + """ + + # Append the search library + full_path = pathlib.Path(os.path.join(self._base_dir,self.libpath)).absolute() + + #Register + self._register_new_library(full_path) + + + # Set the primary properties + self.func = func + self.dim = dim + self.total_budget = total_budget + self.random_seed = random_seed + self.ub = ub + self.lb = lb + + @abstractmethod + def run(self,*args,**kwargs)->None: + r""" + This is a method to kickstart the optimization procedure. + """ + pass + + @staticmethod + def _register_new_library(ppath:Union[pathlib.Path,str])->None: + r""" + This function registers the dynamic library and points the folder to point to + the files required to run some optimization algorithm. + + Args: + ---------- + - ppath: `Union[pathlib.Path,str]`: A composite path to point the location of the library + """ + + # Appends the library path given by parameter + if not isinstance(ppath,pathlib.Path): + # Modify the instance to be an instance of the library `pathlib.Path` + ppath = pathlib.Path(ppath) + + if not ppath.exists(): + raise AttributeError(f"The current path doesn't exist, please check the dependency {ppath.absolute()} exists!", + name="libpath", + obj=ppath) + else: + # Append the library in case this exists + sys.path.append(str(ppath.absolute())) + + # @property + # def libpath(self)->str: + # r""" + # Defines the library path to add + # """ + + # return self.libpath.absolute() + + @property + def full_libpath(self)->str: + r""" + Returns the complete full path to the library + """ + + return str(self._base_dir.joinpath(self.libpath).absolute()) + + @property + def func(self)->Callable: + r""" + Refers to the function object attached to the optimizer + """ + + return self.__func + + @func.setter + def func(self,new_func:Callable)->None: + r""" + This is the setter of the `func` property. + """ + + if callable(new_func): + # Set the function if this is the case + self.__func = new_func + + ### TODO: This is a handler in case the instance of the problem is an IOH instance + if issubclass(type(new_func),RealSingleObjective): + # Write in this case the information about the problem + self.dim = new_func.meta_data.n_variables + self.lb = new_func.bounds.lb + self.ub = new_func.bounds.ub + else: + raise AttributeError("The passed function is not a `callable`!", + name="func", + obj=new_func) + + @func.deleter + def func(self)->bool: + return_val:bool = True + try: + del self.__func + except: + return_val = False + print("There is no function object attached to this class.") + else: + return return_val + + @property + def dim(self)->int: + r""" + This property refers to the dimensionality of the evaluated problem, + and, therefore, the dimensionality handled by the algorithm. + """ + + return self.__dim + + @dim.setter + def dim(self,new_dim)->None: + r""" + Setter of the problem dimensionality + """ + + if isinstance(new_dim,int) and new_dim > 0: + # Change the dimension if the condition of positivity is fulfilled + self.__dim = new_dim + + else: + raise AttributeError("The dimensionality must be a positive integer", + name="new_dim", + obj=new_dim) + + @property + def lb(self)->float: + r""" + The lower bound of the problem + """ + + return self.__lb + + @lb.setter + def lb(self, + new_lb:Union[List[float],np.ndarray,float,int])->None: + r""" + The setter of the lower bound of the problem + """ + + if isinstance(new_lb,(np.ndarray,np.matrix,list)): + warnings.warn("Using the first element as the lower bound!") + new_lb = new_lb[0] + + self.__lb = float(new_lb) + + + + @property + def ub(self)->float: + r""" + The upper bound of the problem + """ + + return self.__ub + + + @ub.setter + def ub(self, + new_ub:Union[List[float],np.ndarray,float])->None: + r""" + The setter of the upper bound of the problem + """ + + if isinstance(new_ub,(np.ndarray,np.matrix,list)): + warnings.warn("Using the first element as the lower bound!") + new_ub = new_ub[0] + + self.__ub = float(new_ub) + + + + @property + def total_budget(self)->int: + r""" + This returns the total budget as a class property + """ + + return self.__total_budget + + @total_budget.setter + def total_budget(self,new_budget:int)->None: + r""" + This sets the total budget of evaluations of the optimizer. + """ + + if isinstance(new_budget,int) and new_budget>0: + self.__total_budget = new_budget + + else: + raise AttributeError("The total budget must be a positive integer", + name="new_budget", + obj=new_budget) + + @property + def random_seed(self)->int: + r""" + Returns the random seed property given to the algorithm + """ + + return self.__random_seed + + @random_seed.setter + def random_seed(self, new_random_seed:int)->None: + r""" + Sets a new random seed to kickstart the algorithm + """ + + if isinstance(new_random_seed,int) and new_random_seed > -1: + # Set the random seed if this condition is fulfilled + self.__random_seed = new_random_seed + + else: + raise AttributeError("The random seed must be a positive integer or 0", + name="new_random_seed", + obj= new_random_seed) + + @property + def opt(self)->Union[Callable,None]: + r""" + A representer of the optimizer + """ + + return self.__opt + + @opt.setter + def opt(self, new_opt:Callable)->None: + r""" + The setter of the optimizer defined for the wrapper. + """ + self.__opt = new_opt + + + @opt.deleter + def opt(self)->bool: + r""" + The deleter object of the optimizer property + """ + return_val:bool = True + try: + del self.__opt + except: + return_val = False + print("There is no optimizer attached to this class.") + else: + return return_val + + ### NOTE: The following functions are only defined for Bayesian Optimizer types, + ### but will be defined within this abstract class for logging purposes. + + @property + def acq_opt_time(self)->float: + return self.get_acq_time() + + def get_acq_time(self)->float: + r""" + Get the time elapsed by the acquisition at each iteration of the algorithm + """ + + if self.opt is not None: + if hasattr(self.opt,"acq_opt_time"): + return getattr(self.opt,"acq_opt_time") + else: + warnings.warn("The optimizer doesn't have the `acq_opt_time` attribute") + return 0 + + else: + raise ValueError("The optimizer is not set") + + @property + def mode_fit_time(self)->float: + return self.get_mode_time() + + + def get_mode_time(self)->float: + r""" + Get the model fitting time + """ + + if self.opt is not None: + if hasattr(self.opt,"mode_fit_time"): + return getattr(self.opt,"mode_fit_time") + else: + warnings.warn("The optimizer doesn't have the `mode_fit_time` attribute") + return 0 + + else: + raise ValueError("The optimizer is not set") + + @property + def cum_iteration_time(self)->float: + return self.get_iter_time() + + + def get_iter_time(self)->float: + r""" + Get the cumulative iteration time + """ + + if self.opt is not None: + if hasattr(self.opt,"cum_iteration_time"): + return getattr(self.opt,"cum_iteration_time") + else: + warnings.warn("The optimizer doesn't have the `cum_iteration_time` attribute") + return 0 + + else: + raise ValueError("The optimizer is not set") + + +class Abstract_Bayesian_Optimizer_Wrapper(Abstract_Optimizer_Wrapper): + r""" + This is a "byproduct" class to manage the Bayesian Optimization Algorithms. + This class just adds a DoE property to define the size of the initial DoE for any corresponding + Bayesian Optimization class algorithm. + """ + + libpath = pathlib.Path("") + + def __init__(self, + func, + dim, + ub, + lb, + DoE_size:Optional[int]=None, + sample_zero:bool = False, + total_budget = ..., + random_seed = ...): + + r""" + This is the initializer for the corresponding Bayesian Optimizers. + """ + + + # Set the primary properties + self.func = func + self.dim = dim + self.total_budget = total_budget + self.random_seed = random_seed + self.ub = ub + self.lb = lb + + # Set the DoE Size + if DoE_size is None: + # Set this to the dimension + DoE_size = dim + + self.Doe_size = DoE_size + + + # This is a new add-on to tell the optimizer to sample a 0 vector on the LHS/Initial sampling stage + self.sample_zero = sample_zero + + # Append the search library + full_path = str(self._base_dir.joinpath(self.libpath)) + + #Register + self._register_new_library(full_path) + + @abstractmethod + def run(self, *args, **kwargs): + pass + + @property + def Doe_size(self)->int: + r""" + This property returns the DoE Size + """ + + return self.__doe_size + + @Doe_size.setter + def Doe_size(self, new_doe_size:int)->int: + r""" + This is the setter of the DoE Size property + """ + + if isinstance(new_doe_size,int) and new_doe_size>0: + self.__doe_size = new_doe_size + + else: + raise AttributeError("The new DoE Size must be a positive integer", + name="new_doe_size", + obj=new_doe_size) + + @property + def sample_zero(self)->bool: + return self.__sample_zero + + @sample_zero.setter + def sample_zero(self,new_change:bool)->None: + try: + new_change = bool(new_change) + except Exception as e: + print(e.args) + + # Set the value + self.__sample_zero = new_change + + + + +class Py_CMA_ES_Wrapper(Abstract_Optimizer_Wrapper): + + r""" + A wrapper for CMA-ES method to compare the other methods. + The library is the one from Nikolaus Hansen. + """ + + + libpath = pathlib.Path(os.path.join('mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization')) + def __init__(self, func, dim, ub, lb, total_budget, random_seed): + + # Use the superclass to initialize this wrapper + super().__init__(func, + dim, + ub, + lb, + total_budget, + random_seed) + + def run(self): + from bayes_optim import RandomForest, BO, GaussianProcess + import cma + from bayes_optim.extension import RealSpace + + import random + space = RealSpace([self.lb, self.ub], random_seed=self.random_seed) * self.dim + ma = float('-inf') + argmax = None + for i in range(10*self.dim): + x = space.sample(1)[0] + + self.opt = cma.fmin + + self.opt(self.func, x, 1., options={'bounds': [ + [self.lb]*self.dim, [self.ub]*self.dim], 'maxfevals': self.total_budget, 'seed': self.random_seed}) + + + +class SaasboWrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + This is the wrapper of SAASBO algorithm. + """ + + libpath = pathlib.Path(os.path.join('mylib', 'lib_saasbo')) + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Initialize the abstract super class + super().__init__(func, + dim, + ub, + lb, + DoE_size, + total_budget, + random_seed, + sample_zero) + + def run(self,**kwargs): + #from saasbo import run_saasbo, get_acq_time, get_mode_time + from saasbo import Saasbo + + + # run_saasbo( + # self.func, + # np.ones(self.dim) * self.ub, + # np.ones(self.dim) * self.lb, + # self.total_budget, + # self.Doe_size, + # self.random_seed, + # alpha=0.01, + # num_warmup=256, + # num_samples=256, + # thinning=32, + # device="cpu", + # ) + + # Use the other properties as given by parameter + alpha:float = float(kwargs.pop("alpha",0.01)) + num_warmup:int = int(kwargs.pop("num_warmup",256)) + num_samples:int = int(kwargs.pop("num_samples",256)) + thinning:int = int(kwargs.pop("thinning",32)) + device:str = str(kwargs.pop("device","cpu")).lower() + + self.opt = Saasbo(func=self.func, + dim=self.dim, + ub=self.ub, + lb=self.lb, + total_budget=self.total_budget, + DoE_size=self.Doe_size, + random_seed=self.random_seed) + + print(self.opt.run_saasbo(self.func,np.ones(self.dim) * self.ub,np.ones(self.dim) * self.lb,self.total_budget, + self.Doe_size, + self.random_seed, + alpha=alpha, + num_warmup=num_warmup, + num_samples=num_samples, + thinning=thinning, + device=device,)) + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + + + +class BO_sklearnWrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + This uses the default Bayesian Optimization defined in + scikit-optimize and derivatives from scikit-learn library. + """ + + libpath = pathlib.Path(os.path.join('mylib', 'lib_BO_sklearn')) + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Initialize the abstract super class + super().__init__(func, + dim, + ub, + lb, + DoE_size, + total_budget, + random_seed, + sample_zero) + + def run(self,**kwargs): + from bosklearn import bosklearn + + acq_func:str = str(kwargs.pop("acq_func","EI")) + noise:float = float(kwargs.pop("noise",0.1**2)) + + self.opt= bosklearn(func=self.func, + dim=self.dim, + ub=self.ub, + lb=self.lb, + total_budget=self.total_budget, + DoE_size=self.Doe_size, + random_seed=self.random_seed) + self.opt.gp_minimize(self.func, # the function to minimize + # the bounds on each dimension of x + list((((self.lb, self.ub),) * self.dim)), + acq_func=acq_func, # the acquisition function + n_calls=self.total_budget, # the number of evaluations of f + n_random_starts=self.Doe_size, # the number of random initialization points + noise=noise, # the noise level (optional) + random_state=self.random_seed) + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + +class BO_bayesoptimWrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" This is the BO of Hao Wang's library""" + + libpath = pathlib.Path(os.path.join('mylib', 'lib_' + "BO_bayesoptim")) + + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Use the super class initializer + super().__init__(func, + dim, + ub, + lb, + DoE_size, + total_budget, + random_seed, + sample_zero) + + print(sys.path) + + def run(self): + from bayes_optim import BO, RealSpace + from bayes_optim.surrogate import GaussianProcess + + space = RealSpace([self.lb, self.ub]) * \ + self.dim # create the search space + + # hyperparameters of the GPR model + thetaL = 1e-10 * (self.ub - self.lb) * np.ones(self.dim) + thetaU = 10 * (self.ub - self.lb) * np.ones(self.dim) + model = GaussianProcess( # create the GPR model + thetaL=thetaL, thetaU=thetaU + ) + + self.opt = BO( + search_space=space, + obj_fun=self.func, + model=model, + DoE_size=self.Doe_size, # number of initial sample points + max_FEs=self.total_budget, # maximal function evaluation + verbose=True + ) + self.opt.run() + + +class BO_development_bayesoptimWrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + This is the latest development from Hao Wang's Library + """ + + libpath = pathlib.Path(os.path.join('mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization')) + # Latest changes from Hao's repository + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Use the superclass initializer + super().__init__(func, + dim, + ub, + lb, + DoE_size, + total_budget, + random_seed, + sample_zero) + + @staticmethod + def _register_new_library(libpath): + r""" + This is a re-interpretation of this function from the `Abstract_Optimizer_Wrapper` class. + The modification with respect to the 'default' function is the advice on where to get the + 'Bayesian-Optimization' module. + + Args: + ---------- + - libpath: `Union[pathlib.Path,str]`: A composite path to point the location of the library + """ + + # Appends the library path given by parameter + if not isinstance(libpath,pathlib.Path): + # Modify the instance to be an instance of the library `pathlib.Path` + libpath = pathlib.Path(libpath) + + if not libpath.exists(): + raise AttributeError(f'No such module Bayesian-Optimization, please consider cloning this repository: https://github.com/wangronin/Bayesian-Optimization to the folder mylib/lib_BO_bayesoptim/', + name="libpath", + obj=libpath) + else: + # Append the library in case this exists + sys.path.append(libpath) + + def run(self): + from bayes_optim.extension import RealSpace + from bayes_optim.bayes_opt import BO + + space = RealSpace([self.lb, self.ub], random_seed=self.random_seed) * self.dim + self.opt = BO( + search_space=space, + obj_fun=self.func, + DoE_size=self.Doe_size, + n_point=1, + random_seed=self.random_seed, + acquisition_optimization={"optimizer": "BFGS"}, + max_FEs=self.total_budget, + verbose=False, + ) + self.opt.run() + +class KPCABOWrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + The KPCABO Method from Kirill and Elena (and the others). + """ + + libpath = pathlib.Path(os.path.join('mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization')) + + # Latest changes from Hao's repository + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Use the superclass initializer + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + + @staticmethod + def _register_new_library(libpath): + r""" + This is a re-interpretation of this function from the `Abstract_Optimizer_Wrapper` class. + The modification with respect to the 'default' function is the advice on where to get the + 'Bayesian-Optimization' module. + + Args: + ---------- + - libpath: `Union[pathlib.Path,str]`: A composite path to point the location of the library + """ + + # Appends the library path given by parameter + if not isinstance(libpath,pathlib.Path): + # Modify the instance to be an instance of the library `pathlib.Path` + libpath = pathlib.Path(libpath) + + if not libpath.exists(): + raise AttributeError(f'No such module Bayesian-Optimization, please consider cloning this repository: https://github.com/wangronin/Bayesian-Optimization to the folder mylib/lib_BO_bayesoptim/', + name="libpath", + obj=libpath) + else: + # Append the library in case this exists + sys.path.insert(0,str(libpath.absolute())) + + print(sys.path) + + def run(self, **kwargs): + + + from bayes_optim import RandomForest, BO, GaussianProcess + + from bayes_optim.extension import PCABO, RealSpace, KernelPCABO, KernelFitStrategy + from bayes_optim.mylogging import eprintf + + import random + + + verbose = bool(kwargs.pop("verbose",False)) + n_point = int((kwargs.pop("n_point",1))) + max_information_loss = float((kwargs.pop("max_information_loss",0.1))) + acquisition_optimizer:str = str(kwargs.pop("acquisition_optimizer","BFGS")) + + space = RealSpace([self.lb, self.ub], random_seed=self.random_seed) * self.dim + + warm_data = None + # This is to define the case to sample the zero during the DoE Stage + if self.sample_zero: + x_init:np.ndarray = np.zeros((1,self.dim)) + fX_init = self.func(x_init) + warm_data = (x_init,fX_init) + self.Doe_size = self.Doe_size-1 + + self.opt = KernelPCABO( + search_space=space, + obj_fun=self.func, + DoE_size=self.Doe_size, + max_FEs=self.total_budget, + verbose=verbose, + n_point=n_point, + acquisition_optimization={"optimizer": acquisition_optimizer}, + max_information_loss=max_information_loss, + kernel_fit_strategy=KernelFitStrategy.AUTO, + NN=self.dim, + random_seed=self.random_seed, + warm_data = warm_data + ) + + print(self.opt.run()) + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + + +class randomWrapper(Abstract_Optimizer_Wrapper): + r""" + A wrapper for Random Search Methods + """ + + libpath = pathlib.Path("") + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): + + self.func = func + self.dim = dim + self.ub = ub + self.lb = lb + self.total_budget = total_budget + self.Doe_size = DoE_size + self.random_seed = random_seed + + def run(self): + import ioh + + def random_search(func:Callable, + search_space, + budget:int): + r""" + Implements random search to minimize a problem of size n. + + Args: + --------------- + - objective_function: The objective function to be minimized. It must accept a vector of size n as input and return a numeric value. + - search_space: A list of tuples, each containing the range of allowable values for each dimension. + - budget: The maximum number of evaluations of the objective function allowed. + + Returns: + --------------- + - best_solution: The best solution found. + - best_score: The minimum value of the objective function associated with the best solution. + """ + best_solution = None + best_score = float('inf') + + for _ in range(budget): + solution = [np.random.uniform(low, high) for (low, high) in search_space] + score = self.func(solution) + + # Update the best solution if necessary + if score < best_score: + best_solution = solution + best_score = score + print(best_score) + return best_solution, best_score + search_space = [(self.lb, self.ub) for _ in range(self.dim)] + budget = self.total_budget + self.opt = random_search + + # Run random search + self.opt(self.func, search_space, budget) + + +class linearPCABOWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + + libpath = pathlib.Path(os.path.join('mylib', 'lib_BO_bayesoptim', 'Bayesian-Optimization')) + + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Call the super initializer + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + + @staticmethod + def _register_new_library(libpath): + r""" + This is a re-interpretation of this function from the `Abstract_Optimizer_Wrapper` class. + The modification with respect to the 'default' function is the advice on where to get the + 'Bayesian-Optimization' module. + + Args: + ---------- + - libpath: `Union[pathlib.Path,str]`: A composite path to point the location of the library + """ + + # Appends the library path given by parameter + if not isinstance(libpath,pathlib.Path): + # Modify the instance to be an instance of the library `pathlib.Path` + libpath = pathlib.Path(libpath) + + if not libpath.exists(): + raise AttributeError(f'No such module Bayesian-Optimization, please consider cloning this repository: https://github.com/wangronin/Bayesian-Optimization to the folder mylib/lib_BO_bayesoptim/', + name="libpath", + obj=libpath) + else: + # Append the library in case this exists + sys.path.insert(0,str(libpath.absolute())) + + def run(self, **kwargs): + + from bayes_optim.extension import PCABO, RealSpace + + # Extract the kwargs to set up the setup + verbose = bool(kwargs.pop("verbose",False)) + n_point = int((kwargs.pop("n_point",1))) + n_components = float((kwargs.pop("n_components",0.9))) + acquisition_optimizer:str = str(kwargs.pop("acquisition_optimizer","BFGS")) + + space = RealSpace([self.lb, self.ub]) * self.dim + + warm_data = None + # This is to define the case to sample the zero during the DoE Stage + if self.sample_zero: + x_init:np.ndarray = np.zeros((1,self.dim)) + fX_init = self.func(x_init) + warm_data = (x_init,fX_init) + self.Doe_size = self.Doe_size-1 + + + + + self.opt = PCABO( + search_space=space, + obj_fun=self.func, + DoE_size=self.Doe_size, + max_FEs=self.total_budget, + verbose=verbose, + n_point=n_point, + n_components=n_components, + acquisition_optimization={"optimizer": acquisition_optimizer}, + random_seed=self.random_seed, + warm_data = warm_data + ) + + print(self.opt.run()) + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + + +class RDUCBWrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + Wrapper of RDUCBW + """ + + libpath = pathlib.Path(os.path.join('mylib', 'lib_RDUCB/HEBO/RDUCB')) + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + # Use the superclass initializer + super().__init__(func, + dim, + ub, + lb, + DoE_size, + total_budget, + random_seed, + sample_zero) + + def run(self): + # import sys + # sys.path.insert(0, "./mylib/lib_linearPCABO/Bayesian-Optimization") + + from hdbo.algorithms import RDUCB + self.opt = RDUCB( algorithm_random_seed=self.random_seed, + eps=-1, + exploration_weight= 'lambda t: 0.5 * np.log(2*t)', + graphSamplingNumIter=100, + learnDependencyStructureRate=1, + lengthscaleNumIter=2, + max_eval=-4, + noise_var= 0.1, + param_n_iter=16, + size_of_random_graph=0.2, + # data_random_seed=self.random_seed, + fn_noise_var=0.15, + grid_size=150, + fn= self.func, + n_iter=self.total_budget-self.Doe_size, + n_rand=self.Doe_size, dim=self.dim,) + self.opt.run() + + + #TODO: This is a correction applied to the method since there is a + # wrapper around this object. + def get_acq_time(self): + return self.opt.mybo.acq_opt_time + + def get_mode_time(self): + return self.opt.mybo.mode_fit_time + + def get_iter_time(self): + return self.opt.mybo.cum_iteration_time + + +class turbo1Wrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + Wrapper of turbo 1 algorithm + """ + + libpath = pathlib.Path(os.path.join('mylib', 'lib_turbo1')) + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Call the Superclass constructor + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + print(sys.path) + + + def run(self, **kwargs): + + # Load the libraries + from turbo import Turbo1 + import torch + import math + import matplotlib + import matplotlib.pyplot as plt + + # Set the specific variables + verbose = bool(kwargs.pop("verbose",True)) + use_ard = bool(kwargs.pop("use_ard",True)) + max_cholesky_size = int(kwargs.pop("max_cholesky_size",2000)) + n_training_steps = int(kwargs.pop("n_training_steps",2000)) + min_cuda = int(kwargs.pop("min_cuda",2000)) + device = str(kwargs.pop("device","cpu")) + dtype = str(kwargs.pop("dtype","float64")) + + + self.opt = Turbo1( + f=self.func, # Handle to objective function + lb=np.ones(self.dim) * self.lb, # Numpy array specifying lower bounds + ub=np.ones(self.dim) * self.ub, # Numpy array specifying upper bounds + n_init=self.Doe_size, # Number of initial bounds from an Latin hypercube design + max_evals=self.total_budget, # Maximum number of evaluations + batch_size=5, # How large batch size TuRBO uses + verbose=verbose, # Print information from each batch + use_ard=use_ard, # Set to true if you want to use ARD for the GP kernel + max_cholesky_size=max_cholesky_size, # When we switch from Cholesky to Lanczos + n_training_steps=n_training_steps, # Number of steps of ADAM to learn the hypers + min_cuda=min_cuda, # Run on the CPU for small datasets + device=device, # "cpu" or "cuda" + dtype=dtype, # float64 or float32 + sample_zero = self.sample_zero # Get to sample zero as part of the function evaluations + ) + self.opt.optimize() + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + + +class turbomWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + libpath = pathlib.Path(os.path.join('mylib', 'lib_turbo1')) + + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Call the Superclass constructor + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + print(sys.path) + + + def run(self,**kwargs) : + from turbo import TurboM + import torch + import math + import matplotlib + import matplotlib.pyplot as plt + #tr = math.floor(self.total_budget / self.Doe_size) - 1 + + # Set the specific variables + verbose = bool(kwargs.pop("verbose",True)) + use_ard = bool(kwargs.pop("use_ard",True)) + max_cholesky_size = int(kwargs.pop("max_cholesky_size",2000)) + n_training_steps = int(kwargs.pop("n_training_steps",2000)) + min_cuda = int(kwargs.pop("n_training_steps",2000)) + batch_size = int(kwargs.pop("batch_size",5)) + device = str(kwargs.pop("device","cpu")) + dtype = str(kwargs.pop("dtype","float64")) + + tr = max(int(self.dim/5),2) + n_init = math.floor(self.Doe_size/tr) + + + self.opt = TurboM( + f=self.func, # Handle to objective function + lb=np.ones(self.dim) * self.lb, # Numpy array specifying lower bounds + ub=np.ones(self.dim) * self.ub, # Numpy array specifying upper bounds + n_init=n_init, # Number of initial bounds from an Symmetric Latin hypercube design + max_evals=self.total_budget, # Maximum number of evaluations + n_trust_regions=tr, # Number of trust regions + batch_size=batch_size, # How large batch size TuRBO uses + verbose=verbose, # Print information from each batch + use_ard=use_ard, # Set to true if you want to use ARD for the GP kernel + max_cholesky_size=max_cholesky_size, # When we switch from Cholesky to Lanczos + n_training_steps=n_training_steps, # Number of steps of ADAM to learn the hypers + min_cuda=min_cuda, # Run on the CPU for small datasets + device=device, # "cpu" or "cuda" + dtype=dtype, # float64 or float32 + sample_zero= self.sample_zero # Switch to sample zero as part of the DoE + ) + self.opt.optimize() + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + +class EBOWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + libpath = pathlib.Path(os.path.join('mylib', 'lib_EBO')) + + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed, sample_zero): + # import sys + # sys.path.append('./mylib/' + 'lib_' + "EBO") + # print(sys.path) + + # Call the Superclass constructor + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + print(sys.path) + + def run(self, **kwargs): + import numpy.matlib as nm + import functools + from ebo_core.ebo import ebo + from test_functions.simple_functions import sample_z + import time + import logging + + dx = self.dim + z = sample_z(dx) + k = np.array([10] * dx) + x_range = nm.repmat([[self.lb], [self.ub]], 1, self.dim) + x_range = x_range.astype(float) + sigma = 0.01 + n = self.Doe_size + budget = self.total_budget + f = self.func + f = functools.partial(lambda f, x: -f(x), f) + options = {'x_range': x_range, # input domain + 'dx': x_range.shape[1], # input dimension + 'max_value': 0, # target value + 'T': budget, # number of iterations + 'B': 1, # number of candidates to be evaluated + 'dim_limit': 3, # max dimension of the input for each additive function component + 'isplot': 0, # 1 if plotting the result; otherwise 0. + 'z': None, 'k': None, # group assignment and number of cuts in the Gibbs sampling subroutine + 'alpha': 1., # hyperparameter of the Gibbs sampling subroutine + 'beta': np.array([5., 2.]), + 'opt_n': 1000, # points randomly sampled to start continuous optimization of acfun + 'pid': 'test3', # process ID for Azure + 'datadir': 'tmp_data/', # temporary data directory for Azure + 'gibbs_iter': 10, # number of iterations for the Gibbs sampling subroutine + 'useAzure': False, # set to True if use Azure for batch evaluation + 'func_cheap': True, # if func cheap, we do not use Azure to test functions + 'n_add': None, # this should always be None. it makes dim_limit complicated if not None. + 'nlayers': 100, # number of the layers of tiles + 'gp_type': 'l1', # other choices are l1, sk, sf, dk, df + 'gp_sigma': 0.1, # noise standard deviation + 'n_bo': 10, # min number of points selected for each partition + 'n_bo_top_percent': 0.5, # percentage of top in bo selections + 'n_top': 10, # how many points to look ahead when doing choose Xnew + 'min_leaf_size': 10, # min number of samples in each leaf + 'max_n_leaves': 10, # max number of leaves + 'thresAzure': 1, # if batch size > thresAzure, we use Azure + 'save_file_name': 'tmp/tmp.pk', + } + self.opt = ebo(f, options) + start = time.time() + self.opt.run() + + print("elapsed time: ", time.time() - start) + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + +class EBO_BWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + libpath = pathlib.Path(os.path.join('mylib', 'lib_EBO')) + + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed,sample_zero): + + # Call the super class initializer + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + + + def run(self, **kwargs): + import numpy.matlib as nm + import functools + from ebo_core.ebo import ebo + from test_functions.simple_functions import sample_z + import time + import logging + + dx = self.dim + z = sample_z(dx) + k = np.array([10] * dx) + x_range = nm.repmat([[self.lb], [self.ub]], 1, self.dim) + x_range = x_range.astype(float) + sigma = 0.01 + n = self.Doe_size + budget = self.total_budget + t= int (float(budget)/10) + + f = self.func + f = functools.partial(lambda f, x: -f(x), f) + + options = {'x_range': x_range, # input domain + 'dx': x_range.shape[1], # input dimension + 'max_value': 0, # target value + 'T': t, # number of iterations + 'B': 10, # number of candidates to be evaluated + 'dim_limit': 3, # max dimension of the input for each additive function component + 'isplot': 0, # 1 if plotting the result; otherwise 0. + 'z': None, 'k': None, # group assignment and number of cuts in the Gibbs sampling subroutine + 'alpha': 1., # hyperparameter of the Gibbs sampling subroutine + 'beta': np.array([5., 2.]), + 'opt_n': 1000, # points randomly sampled to start continuous optimization of acfun + 'pid': 'test3', # process ID for Azure + 'datadir': 'tmp_data/', # temporary data directory for Azure + 'gibbs_iter': 10, # number of iterations for the Gibbs sampling subroutine + 'useAzure': False, # set to True if use Azure for batch evaluation + 'func_cheap': True, # if func cheap, we do not use Azure to test functions + 'n_add': None, # this should always be None. it makes dim_limit complicated if not None. + 'nlayers': 100, # number of the layers of tiles + 'gp_type': 'l1', # other choices are l1, sk, sf, dk, df + 'gp_sigma': 0.1, # noise standard deviation + 'n_bo': 10, # min number of points selected for each partition + 'n_bo_top_percent': 0.5, # percentage of top in bo selections + 'n_top': 10, # how many points to look ahead when doing choose Xnew + 'min_leaf_size': 10, # min number of samples in each leaf + 'max_n_leaves': 10, # max number of leaves + 'thresAzure': 1, # if batch size > thresAzure, we use Azure + 'save_file_name': 'tmp/tmp.pk', + } + self.opt = ebo(f, options) + start = time.time() + self.opt.run() + + print("elapsed time: ", time.time() - start) + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + +class ALEBOWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + r""" + Wrapper of the ALEBO Method + """ + libpath = pathlib.Path("") + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed): + # Call the Super Class + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + + def run(self,**kwargs): + # import pathlib + # my_dir = pathlib.Path(__file__).parent.resolve() + # sys.path.append(os.path.join(my_dir, 'mylib', 'lib_ALEBO')) + # import numpy as np + from ax.utils.measurement.synthetic_functions import branin + print(sys.path) + + def branin_evaluation_function(parameterization): + # Evaluates Branin on the first two parameters of the parameterization. + # Other parameters are unused. + x = np.array([parameterization["x0"], parameterization["x1"]]) + + return {"objective": (branin(x), 0.0)} + + def function(parameterization): + # Evaluates Branin on the first two parameters of the parameterization. + # Other parameters are unused. + x = np.array([parameterization[f'x{i}'] for i in range(self.dim)]) + self.iter+=1 + if self.iter == self.total_budget: + print("Optimization is complete, cannot run another trial.") + exit() + return {"objective": (self.func(x), 0.0)} + + parameters = [ + {"name": "x0", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, + {"name": "x1", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, + ] + parameters.extend([ + {"name": f"x{i}", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"} + for i in range(2, self.dim) + ]) + from ax.modelbridge.strategies.alebo import ALEBOStrategy + alebo_strategy = ALEBOStrategy(D=self.dim, d=4, init_size=self.Doe_size) + alebo_strategy._steps[0].model_kwargs.update({"seed": self.random_seed}) + from ax.service.managed_loop import optimize + self.opt = optimize + + # Call the optimizer + self.opt(parameters=parameters, + experiment_name="test", + objective_name="objective", + evaluation_function=function, + minimize=True, + total_trials=self.total_budget, + generation_strategy=alebo_strategy, + ) + +# def run(self): +# # import sys +# # sys.path.insert(0, "./mylib/lib_linearPCABO/Bayesian-Optimization") +# parameters = [ +# {"name": "x0", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, +# {"name": "x1", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"}, +# ] +# parameters.extend([ +# {"name": f"x{i}", "type": "range", "bounds": [self.lb, self.ub], "value_type": "float"} +# for i in range(2, self.dim) +# ]) +# alebo_strategy = ALEBOStrategy(D=self.dim, d=10, init_size=self.Doe_size) +# from ax.service.managed_loop import optimize +# self.opt = optimize( +# parameters=parameters, +# experiment_name="test", +# objective_name="objective", +# evaluation_function=self.func, +# minimize=True, +# total_trials=self.total_budget, +# generation_strategy=alebo_strategy, +# ) +# self.opt() + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + +class HEBOWrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + Wrapper of the HEBO Method + """ + libpath = pathlib.Path("") + + def __init__(self, func, dim, ub, lb, total_budget, DoE_size, random_seed, sample_zero): + + # Call the Super Class + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + + def run(self,**kwargs): + + import pandas as pd + import numpy as np + from hebo.design_space.design_space import DesignSpace + from hebo.optimizers.hebo import HEBO + # def obj(params: pd.DataFrame) -> np.ndarray: + # return ((params.values - 0.37) ** 2).sum(axis=1).reshape(-1, 1) + def obj(params: pd.DataFrame) -> np.ndarray: + # Imports the desired BBOB function (for example, function #1:sphere) + problem = self.func # parameters: dimensions, index of corresponding BBOB function (1-24) + + # Calcola il valore della funzione obiettivo per ciascuna riga dei parametri + values = [problem(np.squeeze(row.values)) for _, row in params.iterrows()] + + # Restituisci i valori come un array numpy + return np.array(values).reshape(-1, 1) + + dimension_specs = [{"name": f"param{i}", "type": "num", 'lb' : self.lb, 'ub' : self.ub } for i in + range(1, self.dim + 1)] + space = DesignSpace().parse(dimension_specs) + #space = DesignSpace().parse([{'name': 'x', 'type': 'int', 'lb': self.lb, 'ub': self.ub}]) + self.opt:HEBO = HEBO(space, + rand_sample=self.Doe_size, + scramble_seed=self.random_seed ) + + rec = self.opt.suggest(n_suggestions=self.Doe_size) + # Fit initially the model + if self.sample_zero: + # Perform a loop to rewrite the first point from DoE + for idx in range(len(rec.param1)): + rec.param1[idx] = 0.0 + + # Observe the DoE + self.opt.observe(rec, obj(rec)) + + for i in range(self.total_budget-self.Doe_size): + rec = self.opt.suggest(n_suggestions=1) + self.opt.observe(rec, obj(rec)) + print('After %d iterations, best obj is %.2f' % (i, self.opt.y.min())) + self.opt.cum_iteration_time = time.process_time() + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + +class BAxUSWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + r""" + Wrapper of the BAxUS Method + """ + libpath = pathlib.Path(os.path.join("mylib","lib_BAxUS","BAxUS")) + def __init__(self, func, + dim:int, ub, lb, + total_budget:int, + DoE_size, + random_seed, + sample_zero=False, + verbose=False,dtype='float64'): + + # Use the superclass constructor + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + total_budget=total_budget, + DoE_size=DoE_size, + random_seed=random_seed, + sample_zero=sample_zero) + + self._verbose:bool = verbose + self._dtype:str = dtype + + def run(self, **kwargs): + + try: + #from mylib.lib_BAxUS.BAxUS.baxus import BAxUS # Import the BAxUS object + from baxus import BAxUS # Import the BAxUS object + #from mylib.lib_BAxUS.BAxUS.baxus.util.behaviors import BaxusBehavior + from baxus.util.behaviors import BaxusBehavior + #from mylib.lib_BAxUS.BAxUS.baxus.util.behaviors.gp_configuration import GPBehaviour + from baxus.util.behaviors.gp_configuration import GPBehaviour + except ModuleNotFoundError as e: + print("The module was not found!, please download it", e.args) + + # import the parser function from the BAxUS library + #from mylib.lib_BAxUS.BAxUS.baxus.util.parsing import parse + from baxus.util.parsing import parse + #from mylib.lib_BAxUS.BAxUS.baxus.util.parsing import (embedding_type_mapper, + # acquisition_function_mapper, + # mle_optimization_mapper) + + from baxus.util.parsing import (embedding_type_mapper, + acquisition_function_mapper, + mle_optimization_mapper, + fun_mapper) + + #from mylib.lib_BAxUS.BAxUS.baxus.benchmark_runner import fun_mapper, info + from baxus.benchmark_runner import fun_mapper, info + from wrapper_helper import IOH_BAxUS_Wrapper + import logging + + + import json + import logging + import os + import sys + from datetime import datetime + from logging import info, warning + from typing import List + from zlib import adler32 + + + from baxus.util.exceptions import ArgumentError + from baxus.util.utils import star_string + + #FORMAT = "%(asctime)s %(levelname)s: %(filename)s: %(message)s" + #DATEFORMAT = '%m/%d/%Y %I:%M:%S %p' + + # Call the default configuration parser + parser_args:str = f"-f ioh_function --algorithm baxus"\ + f" --input-dim {self.dim} --target-dim {self.dim} --n-init {self.Doe_size}"\ + f" --max-evals {self.total_budget}" + + args = parse(parser_args.split()) + + # directory = os.path.join( + # args.results_dir, + # f"{datetime.now().strftime('%d_%m_%Y')}{f'-{args.run_description}' if len(args.run_description) > 0 else ''}", + # ) + # os.makedirs(directory, exist_ok=True) + # logging.basicConfig( + # filename=os.path.join(directory, "logging.log"), + # level=logging.INFO if not args.verbose else logging.DEBUG, + # format=FORMAT, + # force=True, + # datefmt=DATEFORMAT + # ) + + #sysout_handler = logging.StreamHandler(sys.stdout) + #sysout_handler.setFormatter(logging.Formatter(fmt=FORMAT, datefmt=DATEFORMAT)) + #logging.getLogger().addHandler(sysout_handler) + + #repetitions = list(range(args.num_repetitions)) + + #args_dict = vars(args) + # with open(os.path.join(directory, "conf.json"), "w") as f: + # f.write(json.dumps(args_dict)) + + ### NOTE: Rewriting the definition of the function as: + + + ### + bin_sizing_method = embedding_type_mapper[args.embedding_type] + + acquisition_function = acquisition_function_mapper[args.acquisition_function] + + mle_optimization_method = mle_optimization_mapper[args.mle_optimization] + + input_dim = args.input_dim + target_dim = args.target_dim + + n_init = args.n_init + #n_init = self.Doe_size + + max_evals = args.max_evals + noise_std = args.noise_std + + new_bins_on_split = args.new_bins_on_split + + ## +++++++++++++++++++++++++++++++++++++++++++++++++++ + ## NOTE This handler is in case for small dimensions + if input_dim == 3: + new_bins_on_split = 2 + elif input_dim == 2 or input_dim == 1: + new_bins_on_split = 2 + + ## ++++++++++++++++++++++++++++++++++++++++++++++++++++ + + multistart_samples = args.multistart_samples + mle_training_steps = args.mle_training_steps + multistart_after_samples = args.multistart_after_sample + l_init = args.initial_baselength + l_min = args.min_baselength + l_max = args.max_baselength + adjust_initial_target_dim = True # args.adjust_initial_target_dimension + #print(adjust_initial_target_dim) + budget_until_input_dim = args.budget_until_input_dim + + combs = {} + + if n_init is None: + n_init = target_dim + 1 + if args.min_baselength > args.max_baselength: + raise ArgumentError( + "Minimum baselength has to be larger than maximum baselength." + ) + if args.input_dim < args.target_dim: + raise ArgumentError( + "Input dimension has to be larger than target dimension." + ) + if args.noise_std < 0: + raise ArgumentError("Noise standard deviation has to be positive.") + if max_evals < budget_until_input_dim: + raise ArgumentError("budget_until_input_dim has to be <= max_evals.") + if args.multistart_samples < 1: + raise ArgumentError("Number of multistart samples has to be >= 1.") + if args.multistart_after_sample > args.multistart_samples: + raise ArgumentError( + f"Number of multistart samples after sampling {args.multistart_after_sample} has to be smaller or equal to the numbers" + f"of initial multistart samples {args.multistart_samples}." + ) + if args.multistart_after_sample < 1: + raise ArgumentError( + "Number of multistart samples after sampling has to be >= 1." + ) + if args.mle_training_steps < 0: + raise ArgumentError("Number of mle training steps has to be >= 0.") + if new_bins_on_split < 2: + raise ArgumentError("Number of new bins on split has to be greater than one.") + + funs = { + k: v(dim=input_dim, noise_std=noise_std) + for k, v in fun_mapper().items() + if k == args.function + } + + c = { + f"{k}_in_dim_{v.dim}_t_dim{target_dim}_n_init_{n_init}" + f"{f'_noise_{noise_std}' if noise_std > 0 else ''}": { + "input_dim": v.dim, + "target_dim": min(v.dim, target_dim), + "n_init": n_init, + "f": v, + "lb": v.lb_vec, + "ub": v.ub_vec, + } + for k, v in funs.items() + } + + combs.update(c) + + for i, (k, comb) in enumerate(combs.items()): + info(f"running combination {k}") + llb = comb["lb"] + uub = comb["ub"] + input_dim = comb["input_dim"] + target_dim = comb["target_dim"] + n_init = comb["n_init"] + + #f = comb["f"] + f = IOH_BAxUS_Wrapper(self.func, + self.dim, + self.lb, + self.ub, + None) + + #function_dir = os.path.join(directory, k) + #os.makedirs(function_dir, exist_ok=True) + + if "baxus" == args.algorithm: + # *** BAxUS *** + info("*** BAxUS***") + behavior = BaxusBehavior( + n_new_bins=new_bins_on_split, + initial_base_length=l_init, + min_base_length=l_min, + max_base_length=l_max, + acquisition_function=acquisition_function, + embedding_type=bin_sizing_method, + adjust_initial_target_dim=adjust_initial_target_dim, + noise=noise_std, + budget_until_input_dim=budget_until_input_dim + ) + gp_behaviour = GPBehaviour( + mll_estimation=mle_optimization_method, + n_initial_samples=multistart_samples, + n_best_on_lhs_selection=multistart_after_samples, + n_mle_training_steps=mle_training_steps, + ) + # conf_name = ( + # f"baxus_{behavior}_{gp_behaviour}" + # ) + # run_dir = os.path.join( + # function_dir, + # str(adler32(conf_name.encode("utf-8"))), + # ) + self.opt = BAxUS( + f=f, # Handle to objective function + n_init=n_init, # Number of initial bounds from an Latin hypercube design + max_evals=max_evals, # Maximum number of evaluations + target_dim=target_dim, + #run_dir=run_dir, + #conf_name=conf_name, + behavior=behavior, + gp_behaviour=gp_behaviour, + sample_zero = self.sample_zero + ) + self.opt.optimize() + #del baxus + + + # def obj(params: pd.DataFrame) -> np.ndarray: + # return ((params.values - 0.37) ** 2).sum(axis=1).reshape(-1, 1) + """ def obj(params: pd.DataFrame) -> np.ndarray: + # Imposta la funzione BBOB desiderata (ad esempio, la funzione 1) + problem = self.func # Parametri: dimensione, funzione BBOB (1-24) + + # Calcola il valore della funzione obiettivo per ciascuna riga dei parametri + values = [problem(np.squeeze(row.values)) for _, row in params.iterrows()] + + # Restituisci i valori come un array numpy + return np.array(values).reshape(-1, 1) + + dimension_specs = [{"name": f"param{i}", "type": "num", 'lb' : self.lb, 'ub' : self.ub } for i in + range(1, self.dim + 1)] + space = DesignSpace().parse(dimension_specs) + #space = DesignSpace().parse([{'name': 'x', 'type': 'int', 'lb': self.lb, 'ub': self.ub}]) + self.opt = HEBO(space, rand_sample=self.Doe_size, scramble_seed=self.random_seed ) + for i in range(self.total_budget): + rec = self.opt.suggest(n_suggestions=1) + self.opt.observe(rec, obj(rec)) + print('After %d iterations, best obj is %.2f' % (i, self.opt.y.min())) + self.opt.cum_iteration_time = time.process_time() """ + + # Now the requirement is to wrap the function from the IOH workspace + + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + + + +class SMACWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + #source_lib = os.path.join(__set_directory__,"mylib","lib_BAxUS","BAxUS") + libpath = pathlib.Path("") + def __init__(self, func, + dim:int, ub, lb, + total_budget:int, + DoE_size:int, + random_seed:int, + sample_zero=False, + verbose=False): + # import sys + # sys.path.append('./mylib/' + 'lib_' + "linearPCABO") + # print(sys.path) + # import pathlib + # my_dir = pathlib.Path(__file__).parent.resolve() + # sys.path.append(os.path.join(my_dir, 'mylib', 'lib_RDUCB/HEBO/RDUCB')) + # print(sys.path) + # sys.path.insert(0, bayes_bo_lib) + # print(sys.path) + + # Use the SuperClass Constructor + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + DoE_size=DoE_size, + total_budget=total_budget, + sample_zero=sample_zero, + random_seed=random_seed) + + self._verbose:bool = verbose + + def run(self): + # import sys + # sys.path.insert(0, "./mylib/lib_linearPCABO/Bayesian-Optimization") + + from ConfigSpace import Configuration, ConfigurationSpace, Float + from typing import Callable, List + + from smac import HyperparameterOptimizationFacade, Scenario + from smac.runhistory.dataclasses import TrialValue, TrialInfo + from smac.initial_design.latin_hypercube_design import LatinHypercubeInitialDesign + + + class IOH_SMAC_Wrapper: + + def __init__(self, + f:Callable, + dim:int, + lb:float, + ub:float, + seed:int = 0): + + # Assign the values + self.f = f + self.dim = dim + self.lb = lb + self.ub = ub + self.seed = seed + + @property + def configspace(self) -> ConfigurationSpace: + + # Initialize the configuration space + cs = ConfigurationSpace(seed=self.seed) + + list_of_variables:List[Float] = [] + + for idx_ in range(self.dim): + list_of_variables.append(Float(f"x{str(idx_)}", (self.lb,self.ub), default= 0)) + + cs.add(list_of_variables) + + return cs + + def train(self, config: Configuration, seed: int = 0) -> float: + r""" + This is to follow the same "lexicon" recommended by the library, which + states that a custom made model should have this train function. + """ + + list_of_values:list = [] + + for idx in range(len(config)): + list_of_values.append(config[f"x{idx}"]) + + # Compute the `cost` + cost = self.f(list_of_values) + return cost + + def return_values_to_zero(config_space:ConfigurationSpace)->dict: + r""" + This function changes all the values of the present configuration to zero + """ + + config_space_names = config_space.get_default_configuration() + + + + + model = IOH_SMAC_Wrapper(f=self.func, + dim=self.dim, + lb=self.lb, + ub=self.ub, + seed=self.random_seed) + + # Scenario object + scenario = Scenario(model.configspace, deterministic=False, n_trials=self.total_budget) + + intensifier = HyperparameterOptimizationFacade.get_intensifier( + scenario, + max_config_calls=1, # We basically use one seed per config only + ) + + # Initialize a LHS initial sampler + lhs_des_obj = LatinHypercubeInitialDesign(scenario=scenario, + n_configs=self.Doe_size, + seed=self.random_seed) + + # Now we use SMAC to find the best hyperparameters + self.opt:HyperparameterOptimizationFacade = HyperparameterOptimizationFacade( + scenario, + model.train, + intensifier=intensifier, + initial_design=lhs_des_obj, + overwrite=True, + ) + + # We can ask SMAC which trials should be evaluated next + # Optimize the loop (PENDING THE DOE) + for i in range(self.total_budget): + info = self.opt.ask() + + if i==0 and self.sample_zero: + + new_config = model.configspace.get_default_configuration() + + + + info = TrialInfo(config=new_config, + instance=info.instance, + seed=info.seed, + budget=info.budget) + + + assert info.seed is not None + + cost = model.train(info.config, seed=info.seed) + value = TrialValue(cost=cost, time=0.5) + + self.opt.tell(info, value) + + # After calling ask+tell, we can still optimize + # Note: SMAC will optimize the next 90 trials because 10 trials already have been evaluated + #incumbent = smac.optimize() + + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + +class REMBOWrapper(Abstract_Bayesian_Optimizer_Wrapper): + + libpath = pathlib.Path(os.path.join("mylib","lib_REMBO","HesBO")) + + def __init__(self, func, + dim:int, ub, lb, + total_budget:int, + DoE_size:int, + random_seed:int, + sample_zero:bool = False, + verbose=False): + + # Use the Super Class constructor + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, + total_budget=total_budget, + DoE_size=DoE_size, + random_seed=random_seed, + sample_zero=sample_zero) + + self._verbose:bool = verbose + + def run(self,**kwargs): + + + from typing import Callable, Tuple + try: + from mylib.lib_REMBO.HesBO.REMBO import RunRembo2, RemboSetter + + except ModuleNotFoundError as e: + print("The module was not found, importing with different way!") + from REMBO import RunRembo2, RemboSetter + + # Extract the kwargs to set up REMBO parameterization + matrix_type:str = str(kwargs.pop("matrix_type",'simple')) + kern_inp_type:str = str(kwargs.pop("kern_inp_type",'psi')) + ard:bool = bool(kwargs.pop("ARD",True)) + variance:float = float(kwargs.pop("variance",1.0)) + low_dim:int = int(kwargs.pop("low_dim",max(2,int(self.dim/5)))) + hyper_opt_interval:int = int(kwargs.pop("hyper_opt_interval",2)) + box_size:float = float(kwargs.pop("box_size",np.sqrt(self.dim))) + + # Define a wrapper class for the IOH_Instance + class IOH_REMBO_Wrapper: + def __init__(self, + func:Callable, + dim:int, + lb:float, + ub:float, + act_var=None, + noise_var=0): + + self.range=np.array([(lb,ub)*dim]).reshape((-1,2)) + self.__act_var=act_var + self.__var = noise_var + # Include the function + self.func = func + self.__dim = dim + + def scale_domain(self,x)->np.ndarray: + # Scaling the domain + x_copy = np.copy(x) + if len(x_copy.shape) == 1: + x_copy = x_copy.reshape((1, x_copy.shape[0])) + + for i in range(len(self.range)): + x_copy[:, i] = x_copy[:, i] * (self.range[i,1] - self.range[i,0]) / 2 + ( + self.range[i,1] + self.range[i,0]) / 2 + + new_ar = np.unique(x_copy,axis=0) + return new_ar + + def evaluate_true(self,x): + x_scaled = self.scale_domain(x) + + f = [] + + for idx_,val in enumerate(x_scaled): + f.append(self.func(val)) + + return np.multiply(-1,np.array(f).reshape((-1,1))) + + def evaluate(self, x): + + x_org = self.evaluate_true(x) + n = len(x_org) + + return x_org + np.random.normal(0,self.var,(n,1)) + + + @property + def act_var(self)->np.ndarray: + return self.__act_var + + @act_var.setter + def act_var(self,new_act_var:np.ndarray)->None: + self.__act_var = new_act_var + + @property + def var(self)->float: + return self.__var + + @var.setter + def var(self,new_var)->None: + self.__var = new_var + + + f_wrapper:IOH_REMBO_Wrapper = IOH_REMBO_Wrapper(func=self.func, + dim = self.dim, + lb=self.lb, + ub= self.ub, + noise_var=0) + + self.opt:RemboSetter = RemboSetter(self.random_seed, + sample_zero=self.sample_zero) + # Call the runner + self.opt.optimize(func=f_wrapper, + low_dim=low_dim, + high_dim=self.dim, + initial_n=self.Doe_size, + total_itr=self.total_budget-self.Doe_size, + hyper_opt_interval=hyper_opt_interval, + matrix_type=matrix_type, + kern_inp_type=kern_inp_type, + ARD=ard,variance=variance, + box_size=box_size, + noise_var=0) + + + + # def get_acq_time(self): + # return self.opt.acq_opt_time + + # def get_mode_time(self): + # return self.opt.mode_fit_time + + # def get_iter_time(self): + # return self.opt.cum_iteration_time + + +class BO_Torch_VanillaBO_Wrapper(Abstract_Bayesian_Optimizer_Wrapper): + r""" + This is a wrapper to use a basic code for Vanilla BO by using the most basic + BO-Torch implementation. + """ + libpath = pathlib.Path(os.path.join("mylib","lib_BO_torch_repo")) + + + def __init__(self, func, + dim:int, ub, lb, + total_budget:int, + DoE_size:int, + random_seed:int, + sample_zero:bool=False, + verbose=True): + + # Use the general class initializer + super().__init__(func=func, + dim=dim, + ub=ub, + lb=lb, DoE_size=DoE_size, + total_budget=total_budget, + random_seed=random_seed, + sample_zero=sample_zero) + + # Set an additional verbose property + self._verbose = verbose + + + def run(self,**kwargs): + # Call the Algorithm + try: + from mylib.lib_BO_torch_repo.Algorithms import Vanilla_BO + except ModuleNotFoundError: + # Call the normal way + from Algorithms import Vanilla_BO + + + # Extract the kwargs to set up the experiments + acq_func:str = str(kwargs.pop("acquisition_function","EI")) + beta:float = float(kwargs.pop("beta",0.2)) + + + # Set up the algorithm + self.opt = Vanilla_BO(budget=self.total_budget, + n_DoE=self.Doe_size, + random_seed=self.random_seed, + acquisition_function=acq_func, + verbose = self._verbose, + DoE_parameters = {'sample_zero':self.sample_zero, + 'criterion':"center"} + ) + + self.opt(problem = self.func, + dim = self.dim, + bounds = np.asarray([(self.lb,self.ub) for _ in range(self.dim)]), + beta=beta) + + + +def wrapopt(optimizer_name:str, + func:Callable, + ml_dim:int, + ml_total_budget:int, + ml_DoE_size:int, + random_seed:int, + sample_zero:bool=False): + ub = +5 + lb = -5 + if optimizer_name == "saasbo": + return SaasboWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == "BO_sklearn": + return BO_sklearnWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == "BO_bayesoptim": + return BO_bayesoptimWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == "random": + return randomWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed) + if optimizer_name == "linearPCABO": + return linearPCABOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == "turbo1": + return turbo1Wrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == "turbom": + return turbomWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == 'BO_dev_Hao': + return BO_development_bayesoptimWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == 'EBO': + return EBOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == 'EBO_B': + return EBO_BWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == 'KPCABO': + return KPCABOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == 'pyCMA': + return Py_CMA_ES_Wrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, + random_seed=random_seed) + + if optimizer_name == 'RDUCB': + return RDUCBWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == 'ALEBO': + return ALEBOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + if optimizer_name == 'HEBO': + return HEBOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + + # TODO: NEW ADDENDA TO COMPLY WITH KUDELA, STRIPINIS, RAMIUZKAS + if optimizer_name == "BAxUS": + return BAxUSWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + + if optimizer_name== "SMAC": + return SMACWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + + if optimizer_name=="REMBO": + return REMBOWrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + + + if optimizer_name=="BO_botorch": + return BO_Torch_VanillaBO_Wrapper(func=func, dim=ml_dim, ub=ub, lb=lb, total_budget=ml_total_budget, DoE_size=ml_DoE_size, + random_seed=random_seed,sample_zero=sample_zero) + +if __name__ == "__main__": + dim = 3 + total_budget = 200 + doe_size = dim + seed = 4 + sample_zero = True + + try: + import ioh + except ModuleNotFoundError as e: + print("IOH module not found",e.msg) + + # Algorithm alternatives: + algorithm_name = "REMBO" + #algorithm_name = "turbom" + f = get_problem(1, dimension=dim, instance=1, problem_class=ioh.ProblemClass.BBOB) + + opt = wrapopt(algorithm_name, + f, + dim, + total_budget, + doe_size, + seed, + sample_zero) + opt.run(matrix_type="normal",kern_inp_type="X",ARD=False) diff --git a/wrapper_helper.py b/wrapper_helper.py new file mode 100644 index 0000000..05452cc --- /dev/null +++ b/wrapper_helper.py @@ -0,0 +1,95 @@ +import sys, os + + +try: + from mylib.lib_BAxUS.BAxUS.baxus.benchmarks.benchmark_function import SyntheticBenchmark +except ModuleNotFoundError as e: + print("The benchmark module could not be found! , trying to use another way!!!") + __set_directory__:str= os.path.abspath(os.path.dirname(__file__)) + + sys.path.append(os.path.join(__set_directory__,"mylib","lib_BAxUS","BAxUS")) + from baxus.benchmarks.benchmark_function import SyntheticBenchmark + +import ioh +from typing import List,Tuple,Optional,Union, Callable + +import numpy as np + + + +# This is a helper class to wrap the IOH-BBOB problem instances to run with the BAxUS framework +class IOH_BAxUS_Wrapper(SyntheticBenchmark): + + r""" + This is a wrapper which extends the IOH Single Objective defined instances + to work with BAxUS. + """ + + def __init__(self, + f:Callable, + dim:int, + lb:int, + ub:int, + noise_std: Optional[float] = None, + )->None: + + r""" + Args: + -------- + - f: A IOH Real Single Objective instance + - noise_std: Standard deviation of the observation noise. + - negate: If True, negate the function. + """ + self.__dim = dim + + self.__bounds = [(lb, ub) for _ in range(self.__dim)] + + self.__f:ioh.problem.RealSingleObjective = f + + # Convert the bounds definition + lb_:np.ndarray = np.ravel(np.array(self.__bounds)[:,0]) + ub_:np.ndarray = np.ravel(np.array(self.__bounds)[:,1]) + + # Use the constructor from `SyntheticBenchmark` + super().__init__(dim= self.__dim,lb=lb_,ub=ub_, + noise_std=noise_std) + + def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]])->np.ndarray: + + r""" + This is the implementation of the `__call__` function for this wrapper class. + + Args: + --------- + - x: A list or array with possible points to evaluate. + """ + # Call the __call__ function from the polymorphic class + + + #x =super().__call__(x) + x:np.ndarray = np.array(x) + if x.ndim == 0: + x = np.expand_dims(x, 0) + if x.ndim == 1: + x = np.expand_dims(x, 0) + assert x.ndim == 2 + + result = self.__f(x.ravel()) + + return result + + + @property + def dim(self)->int: + return self.__dim + + @property + def bounds(self)->Union[float,List[float]]: + return self.__bounds + + @property + def IOH_instance(self)->ioh.problem.RealSingleObjective: + return self.__f + + +