From ace566294face7c22bc4e919b787269646503863 Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Sun, 3 Mar 2019 19:00:34 -0500 Subject: [PATCH 01/12] Add a very rough pass for DiscreteEuclideanDomain --- dragonfly/exd/cp_domain_utils.py | 14 ++++++++++ dragonfly/exd/domains.py | 42 ++++++++++++++++++++++++++++ dragonfly/gp/cartesian_product_gp.py | 7 +++-- dragonfly/opt/cp_ga_optimiser.py | 15 ++++++++++ dragonfly/utils/oper_utils.py | 8 ++++++ 5 files changed, 84 insertions(+), 2 deletions(-) diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index 6ed57f0..4672cd4 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -17,6 +17,7 @@ get_original_order_from_reordered_list, \ transpose_list_of_lists from ..utils.oper_utils import random_sample_from_euclidean_domain, \ + random_sample_from_discrete_euclidean_domain, \ random_sample_from_integral_domain, \ random_sample_from_prod_discrete_domain @@ -98,6 +99,8 @@ def load_domain_from_params(domain_params, general_discrete_idxs = [] general_discrete_numeric_items_list = [] general_discrete_numeric_idxs = [] + discrete_euclidean_items_list = [] + discrete_euclidean_idxs = [] raw_name_ordering = [] # We will need the following variables for the function caller and the kernel index_ordering = [] # keeps track of which index goes where in the domain @@ -122,6 +125,13 @@ def load_domain_from_params(domain_params, else: list_of_domains.append(domains.EuclideanDomain(curr_bounds)) index_ordering.append(idx) + elif param['type'] == 'discrete_euclidean': + if param['kernel'] == '': + discrete_euclidean_items_list.extend(curr_items) + discrete_euclidean_idxs.extend(idx) + else: + list_of_domains.append(domains.DiscreteEuclideanDomain(curr_items)) + index_ordering.append(idx) elif param['type'] == 'int': if param['kernel'] == '': general_integral_bounds.extend(curr_bounds) @@ -370,6 +380,10 @@ def sample_from_cp_domain_without_constraints(cp_domain, num_samples, if dom.get_type() == 'euclidean': curr_domain_samples = random_sample_from_euclidean_domain(dom.bounds, num_samples, euclidean_sample_type) + elif dom.get_type() == 'discrete_euclidean': + curr_domain_samples = random_sample_from_discrete_euclidean_domain(dom.valid_vectors, num_samples, + euclidean_sample_type) + elif dom.get_type() == 'integral': curr_domain_samples = random_sample_from_integral_domain(dom.bounds, num_samples, integral_sample_type) diff --git a/dragonfly/exd/domains.py b/dragonfly/exd/domains.py index aa93bdd..5096ef6 100644 --- a/dragonfly/exd/domains.py +++ b/dragonfly/exd/domains.py @@ -8,6 +8,7 @@ import numpy as np from numbers import Number +from scipy.spatial.distance import cdist class Domain(object): """ Domain class. An abstract class which implements domains. """ @@ -143,6 +144,47 @@ def __str__(self): return 'Integral: %s'%(_get_bounds_as_str(self.bounds)) +class DiscreteEuclideanDomain(Domain): + """ Domain for Discrete Euclidean spaces. """ + + def __init__(self, valid_vectors): + """ Constructor. """ + self.valid_vectors = np.array(valid_vectors) + self.diameter = np.linalg.norm(self.valid_vectors.max(0) - self.valid_vectors.min(0)) + self.dim = valid_vectors.shape[1] + self.size = len(valid_vectors) + super(DiscreteEuclideanDomain, self).__init__() + + def get_type(self): + """ Returns the type of the domain. """ + return 'discrete_euclidean' + + def get_dim(self): + """ Return the dimensions. """ + return self.dim + + def is_a_member(self, point): + """ Returns true if point is in the domain. """ + # Naively find the nearest point in the domain + return cdist([point], self.valid_vectors).min() < 1e-8 * self.diameter + + def members_are_equal(self, point_1, point_2): + """ Compares two members and returns True if they are the same. """ + return self.compute_distance(point_1, point_2) < 1e-8 * self.diameter + + @classmethod + def compute_distance(cls, point_1, point_2): + """ Computes the distance between point_1 and point_2. """ + return np.linalg.norm(np.array(point_1) - np.array(point_2)) + + def __str__(self): + """ Returns a string representation. """ + base_str = '%s(%d, %d)'%(self.get_type(), self.size, self.dim) + if self.size < 4: + return '%s: %s'%(base_str, self.valid_vectors) + return base_str + + # Discrete spaces ------------- class DiscreteDomain(Domain): """ A Domain for discrete objects. """ diff --git a/dragonfly/gp/cartesian_product_gp.py b/dragonfly/gp/cartesian_product_gp.py index 0b07fa3..082be94 100644 --- a/dragonfly/gp/cartesian_product_gp.py +++ b/dragonfly/gp/cartesian_product_gp.py @@ -186,6 +186,8 @@ def get_default_kernel_type(domain_type): """ Returns default kernel type for the domain. """ if domain_type == 'euclidean': return _DFLT_DOMAIN_EUC_KERNEL_TYPE + elif domain_type == 'disc_euclidean': + return _DFLT_DOMAIN_EUC_KERNEL_TYPE elif domain_type == 'integral': return _DFLT_DOMAIN_INT_KERNEL_TYPE elif domain_type == 'prod_discrete': @@ -583,6 +585,7 @@ def _get_euc_int_options(dom_type, dom_prefix, options): dom_type_code_dic = {'euclidean': 'euc', 'integral': 'int', 'prod_discrete_numeric': 'disc_num', + 'discrete_euclidean': 'euc', } def _extract_from_options(dom_type_str, property_str): """ Extracts the property from the dom_type. """ @@ -821,7 +824,7 @@ def _build_kernel_for_domain(domain, dom_prefix, kernel_scale, gp_cts_hps, gp_ds kernel_type = _get_kernel_type_from_options(dom_type, 'dom', options) if kernel_type == 'default': kernel_type = get_default_kernel_type(dom.get_type()) - if dom_type in ['euclidean', 'integral', 'prod_discrete_numeric']: + if dom_type in ['euclidean', 'integral', 'prod_discrete_numeric', 'discrete_euclidean']: curr_kernel_hyperparams = _prep_kernel_hyperparams_for_euc_int_kernels( kernel_type, dom, dom_prefix, options) use_same_bw, _, esp_kernel_type, _ = \ @@ -885,7 +888,7 @@ def _get_options_for_domain(dom_type_str): return euc_int_options # Call above functions with relevant arguments. - if dom.get_type() == 'euclidean': + if dom.get_type() in ['euclidean', 'discrete_euclidean']: euc_int_options = _get_options_for_domain('euc') elif dom.get_type() == 'integral': euc_int_options = _get_options_for_domain('int') diff --git a/dragonfly/opt/cp_ga_optimiser.py b/dragonfly/opt/cp_ga_optimiser.py index 348b2b1..96cc201 100644 --- a/dragonfly/opt/cp_ga_optimiser.py +++ b/dragonfly/opt/cp_ga_optimiser.py @@ -42,6 +42,19 @@ def euclidean_gauss_mutation(x, bounds, sigmas=None): ret = _get_gauss_perturbation(x, bounds, sigmas) return _return_ndarray_with_type(x, ret) +def discrete_euclidean_mutation(x, valid_vectors): + """ Makes a change depending on the vector values. """ + # cdist requires 2d input + dists = cdist([x], valid_vectors)[0] + # Exponentiate and normalise to get the probabilities. + unnorm_diff_probs = np.exp(-dists) + sample_diff_probs = unnorm_diff_probs / unnorm_diff_probs.sum() + # Now draw the samples + idxs = np.arange(len(sample_diff_probs)) + idx = np.random.choice(idxs, p=sample_diff_probs) + ret = valid_vectors[idx] + return _return_ndarray_with_type(x, ret) + def integral_gauss_mutation(x, bounds, sigmas=None): """ Defines a Euclidean Mutation. """ ret = _get_gauss_perturbation(x, bounds, sigmas) @@ -91,6 +104,8 @@ def get_default_mutation_op(dom): """ Returns the default mutation operator for the domain. """ if dom.get_type() == 'euclidean': return lambda x: euclidean_gauss_mutation(x, dom.bounds) + elif dom.get_type() == 'disc_euclidean': + return lambda x: discrete_euclidean_mutation(x, dom.valid_vectors) elif dom.get_type() == 'integral': return lambda x: integral_gauss_mutation(x, dom.bounds) elif dom.get_type() == 'discrete': diff --git a/dragonfly/utils/oper_utils.py b/dragonfly/utils/oper_utils.py index 6ebb69f..6112efc 100644 --- a/dragonfly/utils/oper_utils.py +++ b/dragonfly/utils/oper_utils.py @@ -325,6 +325,14 @@ def random_sample_from_euclidean_domain(bounds, num_samples, sample_type='rand') raise ValueError('Unknown sample_type %s.'%(sample_type)) return list(ret) +def random_sample_from_discrete_euclidean_domain(valid_vectors, num_samples, sample_type='rand'): + """ Samples from a Euclidean Domain. """ + if sample_type == 'rand' or True: + ret = valid_vectors[np.random.randint(len(valid_vectors), size=(num_samples, )), :] + else: + raise ValueError('Unknown sample_type %s.'%(sample_type)) + return list(ret) + def random_sample_from_integral_domain(bounds, num_samples, sample_type='rand'): """ Samples from a Integral Domain. """ ret = random_sample_from_euclidean_domain(bounds, num_samples, sample_type) From b3c12c083b030d8bd409f0e9393ba44b37155259 Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Mon, 4 Mar 2019 08:38:21 -0500 Subject: [PATCH 02/12] Add linear interpolation to discrete_euclidean mutations --- dragonfly/opt/cp_ga_optimiser.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dragonfly/opt/cp_ga_optimiser.py b/dragonfly/opt/cp_ga_optimiser.py index 96cc201..9a3ff0e 100644 --- a/dragonfly/opt/cp_ga_optimiser.py +++ b/dragonfly/opt/cp_ga_optimiser.py @@ -42,16 +42,20 @@ def euclidean_gauss_mutation(x, bounds, sigmas=None): ret = _get_gauss_perturbation(x, bounds, sigmas) return _return_ndarray_with_type(x, ret) -def discrete_euclidean_mutation(x, valid_vectors): +def discrete_euclidean_mutation(x, valid_vectors, uniform_prob=0.2): """ Makes a change depending on the vector values. """ # cdist requires 2d input dists = cdist([x], valid_vectors)[0] # Exponentiate and normalise to get the probabilities. unnorm_diff_probs = np.exp(-dists) sample_diff_probs = unnorm_diff_probs / unnorm_diff_probs.sum() + # Change the distribution to interplate between it and uniform + n = sample_diff_probs.shape[0] + unif = np.full(sample_diff_probs.shape, 1. / n) + p = (1 - uniform_prob) * sample_diff_probs + uniform_prob * unif # Now draw the samples - idxs = np.arange(len(sample_diff_probs)) - idx = np.random.choice(idxs, p=sample_diff_probs) + idxs = np.arange(n) + idx = np.random.choice(idxs, p=p) ret = valid_vectors[idx] return _return_ndarray_with_type(x, ret) From e620ce5d9dd6539bc1218b1157b23ab6cc8093dd Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Mon, 4 Mar 2019 08:38:57 -0500 Subject: [PATCH 03/12] Add comment about discrete euclidean domain --- dragonfly/gp/cartesian_product_gp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dragonfly/gp/cartesian_product_gp.py b/dragonfly/gp/cartesian_product_gp.py index 082be94..01fe68c 100644 --- a/dragonfly/gp/cartesian_product_gp.py +++ b/dragonfly/gp/cartesian_product_gp.py @@ -23,6 +23,8 @@ # Part 1: Parameters and Arguments # ================================ +# NOTE: The discrete euclidean domain will use the same parameters as the euclidean domain. + # Domain kernel parameters _DFLT_DOMAIN_EUC_KERNEL_TYPE = 'matern' _DFLT_DOMAIN_INT_KERNEL_TYPE = 'matern' From 9e50e854357e49158505c062336b7e34055499c9 Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Mon, 4 Mar 2019 08:40:13 -0500 Subject: [PATCH 04/12] Add domain_discrete_euclidean_sample_type to function args --- dragonfly/exd/cp_domain_utils.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index 4672cd4..f9ca89d 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -323,6 +323,7 @@ def sample_from_cp_domain(cp_domain, num_samples, domain_samplers=None, euclidean_sample_type='rand', integral_sample_type='rand', nn_sample_type='rand', + discrete_euclidean_sample_type='rand', max_num_retries_for_constraint_satisfaction=10, verbose_constraint_satisfaction=True): """ Samples from the CP domain. """ @@ -334,7 +335,7 @@ def sample_from_cp_domain(cp_domain, num_samples, domain_samplers=None, for _ in range(max_num_retries_for_constraint_satisfaction): curr_ret = sample_from_cp_domain_without_constraints(cp_domain, num_samples_to_draw, domain_samplers, euclidean_sample_type, integral_sample_type, - nn_sample_type) + nn_sample_type, discrete_euclidean_sample_type) # Check constraints if cp_domain.has_constraints(): constraint_satisfying_ret = [elem for elem in curr_ret if @@ -368,7 +369,8 @@ def sample_from_cp_domain_without_constraints(cp_domain, num_samples, domain_samplers=None, euclidean_sample_type='rand', integral_sample_type='rand', - nn_sample_type='rand'): + nn_sample_type='rand', + discrete_euclidean_sample_type='rand'): """ Samples from the CP domain without the constraints. """ if domain_samplers is None: domain_samplers = [None] * cp_domain.num_domains @@ -382,7 +384,7 @@ def sample_from_cp_domain_without_constraints(cp_domain, num_samples, euclidean_sample_type) elif dom.get_type() == 'discrete_euclidean': curr_domain_samples = random_sample_from_discrete_euclidean_domain(dom.valid_vectors, num_samples, - euclidean_sample_type) + discrete_euclidean_sample_type) elif dom.get_type() == 'integral': curr_domain_samples = random_sample_from_integral_domain(dom.bounds, num_samples, @@ -399,7 +401,8 @@ def sample_from_cp_domain_without_constraints(cp_domain, num_samples, curr_domain_samples = sample_from_cp_domain(dom, num_samples, euclidean_sample_type=euclidean_sample_type, integral_sample_type=integral_sample_type, - nn_sample_type=nn_sample_type) + nn_sample_type=nn_sample_type, + discrete_euclidean_sample_type=discrete_euclidean_sample_type) else: raise ValueError('Unknown domain type %s. Provide sampler.'%(dom.get_type())) individual_domain_samples.append(curr_domain_samples) @@ -411,13 +414,14 @@ def sample_from_config_space(config, num_samples, domain_samplers=None, fidel_space_euclidean_sample_type='rand', fidel_space_integral_sample_type='rand', - domain_euclidean_sample_type='rand', + domain_discrete_euclidean_sample_type='rand', domain_integral_sample_type='rand', domain_nn_sample_type='rand', + domain_euclidean_sample_type='rand', ): """ Samples from the Domain and possibly the fidelity space. """ domain_samples = sample_from_cp_domain(config.domain, num_samples, domain_samplers, - domain_euclidean_sample_type, domain_integral_sample_type, domain_nn_sample_type) + domain_euclidean_sample_type, domain_integral_sample_type, domain_nn_sample_type, domain_discrete_euclidean_sample_type=domain_discrete_euclidean_sample_type) if hasattr(config, 'fidel_space'): fidel_space_samples = sample_from_cp_domain(config.fidel_space, num_samples, fidel_space_samplers, fidel_space_euclidean_sample_type, From 62f29c96993c55229a27d23ff8ec292c48e4b40f Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Tue, 5 Mar 2019 08:48:30 -0500 Subject: [PATCH 05/12] More fixes --- dragonfly/gp/cartesian_product_gp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dragonfly/gp/cartesian_product_gp.py b/dragonfly/gp/cartesian_product_gp.py index 01fe68c..d85f396 100644 --- a/dragonfly/gp/cartesian_product_gp.py +++ b/dragonfly/gp/cartesian_product_gp.py @@ -24,6 +24,7 @@ # ================================ # NOTE: The discrete euclidean domain will use the same parameters as the euclidean domain. +# TODO(kirthevasank) Add more information about this note. # Domain kernel parameters _DFLT_DOMAIN_EUC_KERNEL_TYPE = 'matern' @@ -188,7 +189,7 @@ def get_default_kernel_type(domain_type): """ Returns default kernel type for the domain. """ if domain_type == 'euclidean': return _DFLT_DOMAIN_EUC_KERNEL_TYPE - elif domain_type == 'disc_euclidean': + elif domain_type == 'discrete_euclidean': return _DFLT_DOMAIN_EUC_KERNEL_TYPE elif domain_type == 'integral': return _DFLT_DOMAIN_INT_KERNEL_TYPE @@ -621,6 +622,7 @@ def _extract_from_options(property_str): def _get_kernel_type_from_options(dom_type, dom_prefix, options): """ Returns kernel type from options. """ dom_type_descr_dict = {'euclidean': 'euc', + 'discrete_euclidean': 'euc', 'integral': 'int', 'prod_discrete_numeric': 'disc_num', 'prod_discrete': 'disc', From 88f09c64e1fd41c07ae45bb60501580b4b8e8272 Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Wed, 6 Mar 2019 21:37:34 -0500 Subject: [PATCH 06/12] Fix typo --- dragonfly/exd/cp_domain_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index f9ca89d..88b537c 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -414,10 +414,10 @@ def sample_from_config_space(config, num_samples, domain_samplers=None, fidel_space_euclidean_sample_type='rand', fidel_space_integral_sample_type='rand', - domain_discrete_euclidean_sample_type='rand', + domain_euclidean_sample_type='rand', domain_integral_sample_type='rand', domain_nn_sample_type='rand', - domain_euclidean_sample_type='rand', + domain_discrete_euclidean_sample_type='rand', ): """ Samples from the Domain and possibly the fidelity space. """ domain_samples = sample_from_cp_domain(config.domain, num_samples, domain_samplers, From 2290df7f82993a7fbfbeb898dd4ad2494f26d1bf Mon Sep 17 00:00:00 2001 From: kirthevasank Date: Sat, 30 Mar 2019 12:51:24 -0400 Subject: [PATCH 07/12] added an example in supernova for declaring domains in code. --- examples/supernova/in_code_demo.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/supernova/in_code_demo.py diff --git a/examples/supernova/in_code_demo.py b/examples/supernova/in_code_demo.py new file mode 100644 index 0000000..33ba719 --- /dev/null +++ b/examples/supernova/in_code_demo.py @@ -0,0 +1,29 @@ +""" + In code demo for supernova experiment. + -- kandasamy@cs.cmu.edu +""" + +from argparse import Namespace +from dragonfly import load_config, maximise_function +# From current directory +from snls import objective + + +def main(): + """ Main function. """ + domain_vars = [{'name': 'hubble_constant', 'type': 'float', 'min': 60, 'max': 80}, + {'name': 'omega_m', 'type': 'float', 'min': 0, 'max': 1}, + {'name': 'omega_l', 'type': 'float', 'min': 0, 'max': 1}] + config_params = {'domain': domain_vars} + config = load_config(config_params) + objective = + max_capital = 2 * 60 * 60 # Optimisation budget in seconds + + # Optimise + opt_pt, opt_val, history = maximise_function(objective, config.domain, + max_capital, capital_type='realtime', config=config) + + +if __name__ == '__main__': + main() + From f8350162cdd2685dad42114d74cd762cf944d744 Mon Sep 17 00:00:00 2001 From: kirthevasank Date: Sat, 30 Mar 2019 13:01:04 -0400 Subject: [PATCH 08/12] Wrote up an example and fixed some minor issues. Not ready to be run yet. --- dragonfly/__init__.py | 4 ++ dragonfly/exd/cp_domain_utils.py | 56 ++++++++++++---------- dragonfly/exd/domains.py | 79 +++++++++++++++----------------- dragonfly/utils/oper_utils.py | 3 +- 4 files changed, 76 insertions(+), 66 deletions(-) diff --git a/dragonfly/__init__.py b/dragonfly/__init__.py index 479b19c..37c7d03 100644 --- a/dragonfly/__init__.py +++ b/dragonfly/__init__.py @@ -12,6 +12,7 @@ -- kandasamy@cs.cmu.edu """ +# Main APIs from .apis.opt import maximise_function, minimise_function, \ maximise_multifidelity_function, minimise_multifidelity_function, \ maximize_function, minimize_function, \ @@ -20,3 +21,6 @@ multiobjective_minimise_functions, \ multiobjective_maximize_functions, \ multiobjective_minimize_functions + +# Handling Configuration Files/Parameters +from .exd.cp_domain_utils import load_config_file, load_config diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index 88b537c..9b19cb9 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -45,20 +45,25 @@ def _process_fidel_to_opt(raw_fidel_to_opt, fidel_space, fidel_space_orderings, def load_config_file(config_file, *args, **kwargs): """ Loads the configuration file. """ parsed_result = config_parser(config_file) - domain_params = parsed_result['domain'] - domain_constraints = None if not ('domain_constraints' in parsed_result.keys()) \ - else parsed_result['domain_constraints'] + return load_config(parsed_result, config_file, *args, **kwargs) + + +def load_config(config_parameters, config_file=None, *args, **kwargs): + """ Loads configuration from parameters. """ + domain_params = config_parameters['domain'] + domain_constraints = None if not ('domain_constraints' in config_parameters.keys()) \ + else config_parameters['domain_constraints'] domain_info = Namespace(config_file=config_file) domain, domain_orderings = load_domain_from_params(domain_params, domain_constraints=domain_constraints, domain_info=domain_info, *args, **kwargs) - config = Namespace(name=parsed_result['exp_info']['name'], + config = Namespace(name=config_parameters['exp_info']['name'], domain=domain, domain_orderings=domain_orderings) # Check the fidelity space - if 'fidel_space' in parsed_result.keys(): - fidel_space_params = parsed_result['fidel_space'] + if 'fidel_space' in config_parameters.keys(): + fidel_space_params = config_parameters['fidel_space'] fidel_space_constraints = None if not ('fidel_space_constraints' in - parsed_result.keys()) \ - else parsed_result['fidel_space_constraints'] + config_parameters.keys()) \ + else config_parameters['fidel_space_constraints'] fidel_space_info = Namespace(config_file=config_file) fidel_space, fidel_space_orderings = load_domain_from_params( fidel_space_params, domain_constraints=fidel_space_constraints, @@ -67,7 +72,8 @@ def load_config_file(config_file, *args, **kwargs): config.fidel_space = fidel_space config.fidel_space_orderings = fidel_space_orderings config.raw_fidel_to_opt, config.fidel_to_opt = _process_fidel_to_opt( - parsed_result['fidel_to_opt'], fidel_space, fidel_space_orderings, config_file) + config_parameters['fidel_to_opt'], fidel_space, fidel_space_orderings, + config_file) return config @@ -126,12 +132,12 @@ def load_domain_from_params(domain_params, list_of_domains.append(domains.EuclideanDomain(curr_bounds)) index_ordering.append(idx) elif param['type'] == 'discrete_euclidean': - if param['kernel'] == '': - discrete_euclidean_items_list.extend(curr_items) - discrete_euclidean_idxs.extend(idx) - else: - list_of_domains.append(domains.DiscreteEuclideanDomain(curr_items)) - index_ordering.append(idx) + if param['kernel'] == '': + discrete_euclidean_items_list.extend(curr_items) + discrete_euclidean_idxs.extend(idx) + else: + list_of_domains.append(domains.DiscreteEuclideanDomain(curr_items)) + index_ordering.append(idx) elif param['type'] == 'int': if param['kernel'] == '': general_integral_bounds.extend(curr_bounds) @@ -383,9 +389,9 @@ def sample_from_cp_domain_without_constraints(cp_domain, num_samples, curr_domain_samples = random_sample_from_euclidean_domain(dom.bounds, num_samples, euclidean_sample_type) elif dom.get_type() == 'discrete_euclidean': - curr_domain_samples = random_sample_from_discrete_euclidean_domain(dom.valid_vectors, num_samples, - discrete_euclidean_sample_type) - + curr_domain_samples = random_sample_from_discrete_euclidean_domain( + dom.list_of_items, num_samples, + discrete_euclidean_sample_type) elif dom.get_type() == 'integral': curr_domain_samples = random_sample_from_integral_domain(dom.bounds, num_samples, integral_sample_type) @@ -399,10 +405,10 @@ def sample_from_cp_domain_without_constraints(cp_domain, num_samples, dom.constraint_checker) elif dom.get_type() == 'cartesian_product': curr_domain_samples = sample_from_cp_domain(dom, num_samples, - euclidean_sample_type=euclidean_sample_type, - integral_sample_type=integral_sample_type, - nn_sample_type=nn_sample_type, - discrete_euclidean_sample_type=discrete_euclidean_sample_type) + euclidean_sample_type=euclidean_sample_type, + integral_sample_type=integral_sample_type, + nn_sample_type=nn_sample_type, + discrete_euclidean_sample_type=discrete_euclidean_sample_type) else: raise ValueError('Unknown domain type %s. Provide sampler.'%(dom.get_type())) individual_domain_samples.append(curr_domain_samples) @@ -414,6 +420,7 @@ def sample_from_config_space(config, num_samples, domain_samplers=None, fidel_space_euclidean_sample_type='rand', fidel_space_integral_sample_type='rand', + fidel_space_discrete_euclidean_sample_type='rand', domain_euclidean_sample_type='rand', domain_integral_sample_type='rand', domain_nn_sample_type='rand', @@ -421,11 +428,12 @@ def sample_from_config_space(config, num_samples, ): """ Samples from the Domain and possibly the fidelity space. """ domain_samples = sample_from_cp_domain(config.domain, num_samples, domain_samplers, - domain_euclidean_sample_type, domain_integral_sample_type, domain_nn_sample_type, domain_discrete_euclidean_sample_type=domain_discrete_euclidean_sample_type) + domain_euclidean_sample_type, domain_integral_sample_type, domain_nn_sample_type, + domain_discrete_euclidean_sample_type) if hasattr(config, 'fidel_space'): fidel_space_samples = sample_from_cp_domain(config.fidel_space, num_samples, fidel_space_samplers, fidel_space_euclidean_sample_type, - fidel_space_integral_sample_type) + fidel_space_integral_sample_type, fidel_space_discrete_euclidean_sample_type) return [list(x) for x in zip(fidel_space_samples, domain_samples)] else: return domain_samples diff --git a/dragonfly/exd/domains.py b/dragonfly/exd/domains.py index 5096ef6..9f4410d 100644 --- a/dragonfly/exd/domains.py +++ b/dragonfly/exd/domains.py @@ -144,47 +144,6 @@ def __str__(self): return 'Integral: %s'%(_get_bounds_as_str(self.bounds)) -class DiscreteEuclideanDomain(Domain): - """ Domain for Discrete Euclidean spaces. """ - - def __init__(self, valid_vectors): - """ Constructor. """ - self.valid_vectors = np.array(valid_vectors) - self.diameter = np.linalg.norm(self.valid_vectors.max(0) - self.valid_vectors.min(0)) - self.dim = valid_vectors.shape[1] - self.size = len(valid_vectors) - super(DiscreteEuclideanDomain, self).__init__() - - def get_type(self): - """ Returns the type of the domain. """ - return 'discrete_euclidean' - - def get_dim(self): - """ Return the dimensions. """ - return self.dim - - def is_a_member(self, point): - """ Returns true if point is in the domain. """ - # Naively find the nearest point in the domain - return cdist([point], self.valid_vectors).min() < 1e-8 * self.diameter - - def members_are_equal(self, point_1, point_2): - """ Compares two members and returns True if they are the same. """ - return self.compute_distance(point_1, point_2) < 1e-8 * self.diameter - - @classmethod - def compute_distance(cls, point_1, point_2): - """ Computes the distance between point_1 and point_2. """ - return np.linalg.norm(np.array(point_1) - np.array(point_2)) - - def __str__(self): - """ Returns a string representation. """ - base_str = '%s(%d, %d)'%(self.get_type(), self.size, self.dim) - if self.size < 4: - return '%s: %s'%(base_str, self.valid_vectors) - return base_str - - # Discrete spaces ------------- class DiscreteDomain(Domain): """ A Domain for discrete objects. """ @@ -252,6 +211,44 @@ def is_a_member(self, point): return discrete_numeric_element_is_in_list(point, self.list_of_items) +class DiscreteEuclideanDomain(DiscreteDomain): + """ Domain for Discrete Euclidean spaces. """ + + def __init__(self, list_of_items): + """ Constructor. """ + list_of_items = np.array(list_of_items) + self.dim = list_of_items.shape[1] + self.size = len(list_of_items) + self.diameter = np.sqrt(self.dim) * (list_of_items.max() - list_of_items.min()) + super(DiscreteEuclideanDomain, self).__init__(list_of_items) + + def get_type(self): + """ Returns the type of the domain. """ + return 'discrete_euclidean' + + def _get_disc_domain_type(self): + """ Prefix for __str__. Can be overridden by a child class. """ + return "DiscEuc" + + def get_dim(self): + """ Return the dimensions. """ + return self.dim + + @classmethod + def compute_distance(cls, point_1, point_2): + """ Computes the distance between point_1 and point_2. """ + return np.linalg.norm(np.array(point_1) - np.array(point_2)) + + def is_a_member(self, point): + """ Returns true if point is in the domain. """ + # Naively find the nearest point in the domain + return cdist([point], self.list_of_items).min() < 1e-8 * self.diameter + + def members_are_equal(self, point_1, point_2): + """ Compares two members and returns True if they are the same. """ + return self.compute_distance(point_1, point_2) < 1e-8 * self.diameter + + # A product of discrete spaces ----------------------------------------------------- class ProdDiscreteDomain(Domain): """ A product of discrete objects. """ diff --git a/dragonfly/utils/oper_utils.py b/dragonfly/utils/oper_utils.py index 6112efc..bfde805 100644 --- a/dragonfly/utils/oper_utils.py +++ b/dragonfly/utils/oper_utils.py @@ -325,7 +325,8 @@ def random_sample_from_euclidean_domain(bounds, num_samples, sample_type='rand') raise ValueError('Unknown sample_type %s.'%(sample_type)) return list(ret) -def random_sample_from_discrete_euclidean_domain(valid_vectors, num_samples, sample_type='rand'): +def random_sample_from_discrete_euclidean_domain(valid_vectors, num_samples, + sample_type='rand'): """ Samples from a Euclidean Domain. """ if sample_type == 'rand' or True: ret = valid_vectors[np.random.randint(len(valid_vectors), size=(num_samples, )), :] From e3ba9f484e7c05b914ab4cbd0bbf67ed377242f0 Mon Sep 17 00:00:00 2001 From: kirthevasank Date: Sat, 30 Mar 2019 20:01:21 -0400 Subject: [PATCH 09/12] Added in_code_demo for Hartmann6_4 and fixed other issues. --- dragonfly/exd/cp_domain_utils.py | 63 ++++++++++++++++--- dragonfly/exd/domains.py | 3 +- examples/supernova/in_code_demo.py | 6 +- .../synthetic/hartmann6_4/in_code_demo.py | 53 ++++++++++++++++ 4 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 examples/synthetic/hartmann6_4/in_code_demo.py diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index 9b19cb9..52695d0 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -10,6 +10,7 @@ from __future__ import print_function from argparse import Namespace +from copy import deepcopy # Local imports from . import domains from ..parse.config_parser import config_parser @@ -42,28 +43,69 @@ def _process_fidel_to_opt(raw_fidel_to_opt, fidel_space, fidel_space_orderings, return raw_fidel_to_opt, fidel_to_opt +def _preprocess_domain_parameters(domain_parameters, var_prefix='var_'): + """ Preprocesses domain parameters in a configuration specification. """ + for idx, var_dict in enumerate(domain_parameters): + if not 'name' in var_dict.keys(): + var_dict['name'] = '%s%02d'%(var_prefix, idx) + if not 'dim' in var_dict.keys(): + var_dict['dim'] = '' + if not 'kernel' in var_dict.keys(): + var_dict['kernel'] = '' + if var_dict['type'] in ['float', 'int']: + if not ('min' in var_dict.keys() and 'max' in var_dict.keys()): + if not 'bounds' in var_dict.keys(): + raise ValueError('Specify bounds or min and max for Euclidean and Integral ' + + 'variables: %s.'%(var_dict)) + else: + var_dict['min'] = var_dict['bounds'][0] + var_dict['max'] = var_dict['bounds'][1] + print(domain_parameters) + return domain_parameters + + +def _preprocess_config_params(config_params): + """ Preprocesses configuration parameters. """ + config_params = deepcopy(config_params) + # The name of the experiment + if not 'name' in config_params: + if 'exp_info' in config_params and 'name' in config_params['exp_info']: + config_params['name'] = config_params['exp_info']['name'] + else: + config_params['name'] = 'no_name' + # Process the domain variables + config_params['domain'] = _preprocess_domain_parameters(config_params['domain'], + var_prefix='domvar_') + if 'fidel_space' in config_params: + config_params['fidel_space'] = _preprocess_domain_parameters( + config_params['fidel_space'], var_prefix='fidelvar_') + return config_params + + def load_config_file(config_file, *args, **kwargs): """ Loads the configuration file. """ parsed_result = config_parser(config_file) + # If loading from file, no need to pre-process return load_config(parsed_result, config_file, *args, **kwargs) -def load_config(config_parameters, config_file=None, *args, **kwargs): +def load_config(config_params, config_file=None, *args, **kwargs): """ Loads configuration from parameters. """ - domain_params = config_parameters['domain'] - domain_constraints = None if not ('domain_constraints' in config_parameters.keys()) \ - else config_parameters['domain_constraints'] + config_params = _preprocess_config_params(config_params) + domain_params = config_params['domain'] + domain_constraints = None if not ('domain_constraints' in config_params.keys()) \ + else config_params['domain_constraints'] domain_info = Namespace(config_file=config_file) domain, domain_orderings = load_domain_from_params(domain_params, domain_constraints=domain_constraints, domain_info=domain_info, *args, **kwargs) - config = Namespace(name=config_parameters['exp_info']['name'], + config = Namespace(name=config_params['name'], domain=domain, domain_orderings=domain_orderings) # Check the fidelity space - if 'fidel_space' in config_parameters.keys(): - fidel_space_params = config_parameters['fidel_space'] + if 'fidel_space' in config_params.keys(): + fidel_space_params = config_params['fidel_space'] fidel_space_constraints = None if not ('fidel_space_constraints' in - config_parameters.keys()) \ - else config_parameters['fidel_space_constraints'] + config_params.keys()) \ + else config_params['fidel_space_constraints'] fidel_space_info = Namespace(config_file=config_file) fidel_space, fidel_space_orderings = load_domain_from_params( fidel_space_params, domain_constraints=fidel_space_constraints, @@ -72,7 +114,7 @@ def load_config(config_parameters, config_file=None, *args, **kwargs): config.fidel_space = fidel_space config.fidel_space_orderings = fidel_space_orderings config.raw_fidel_to_opt, config.fidel_to_opt = _process_fidel_to_opt( - config_parameters['fidel_to_opt'], fidel_space, fidel_space_orderings, + config_params['fidel_to_opt'], fidel_space, fidel_space_orderings, config_file) return config @@ -427,6 +469,7 @@ def sample_from_config_space(config, num_samples, domain_discrete_euclidean_sample_type='rand', ): """ Samples from the Domain and possibly the fidelity space. """ + # pylint: disable=too-many-arguments domain_samples = sample_from_cp_domain(config.domain, num_samples, domain_samplers, domain_euclidean_sample_type, domain_integral_sample_type, domain_nn_sample_type, domain_discrete_euclidean_sample_type) diff --git a/dragonfly/exd/domains.py b/dragonfly/exd/domains.py index 9f4410d..8b6a492 100644 --- a/dragonfly/exd/domains.py +++ b/dragonfly/exd/domains.py @@ -354,7 +354,8 @@ def __init__(self, list_of_domains, domain_info=None): self.get_raw_point = lambda x: get_raw_point_from_processed_point(x, self, self.domain_info.config_orderings.index_ordering, self.domain_info.config_orderings.dim_ordering) - if hasattr(self.domain_info, 'config_file'): + if hasattr(self.domain_info, 'config_file') and \ + self.domain_info.config_file is not None: import os self.config_file = self.domain_info.config_file self.config_file_dir = os.path.dirname(os.path.abspath(os.path.realpath( diff --git a/examples/supernova/in_code_demo.py b/examples/supernova/in_code_demo.py index 33ba719..3a62c78 100644 --- a/examples/supernova/in_code_demo.py +++ b/examples/supernova/in_code_demo.py @@ -3,10 +3,9 @@ -- kandasamy@cs.cmu.edu """ -from argparse import Namespace from dragonfly import load_config, maximise_function # From current directory -from snls import objective +from snls import objective as snls_objective def main(): @@ -16,11 +15,10 @@ def main(): {'name': 'omega_l', 'type': 'float', 'min': 0, 'max': 1}] config_params = {'domain': domain_vars} config = load_config(config_params) - objective = max_capital = 2 * 60 * 60 # Optimisation budget in seconds # Optimise - opt_pt, opt_val, history = maximise_function(objective, config.domain, + opt_pt, opt_val, history = maximise_function(snls_objective, config.domain, max_capital, capital_type='realtime', config=config) diff --git a/examples/synthetic/hartmann6_4/in_code_demo.py b/examples/synthetic/hartmann6_4/in_code_demo.py new file mode 100644 index 0000000..c25e3d4 --- /dev/null +++ b/examples/synthetic/hartmann6_4/in_code_demo.py @@ -0,0 +1,53 @@ +""" + In code demo for Hartmann6_4 + -- kandasamy@cs.cmu.edu +""" + +from dragonfly import load_config, maximise_function, maximise_multifidelity_function +# From current directory +from hartmann6_4 import objective +from hartmann6_4_mf import objective as mf_objective +from hartmann6_4_mf import cost as mf_cost + + +def main(): + """ Main function. """ + # First Specify all parameters + domain_vars = [{'type': 'int', 'min': 224, 'max': 324, 'dim': 1}, + {'type': 'float', 'min': 0, 'max': 10, 'dim': 2}, + {'type': 'float', 'min': 0, 'max': 1, 'dim': 1}, + {'type': 'int', 'min': 0, 'max': 92, 'dim': 2}, + ] + fidel_vars = [{'type': 'float', 'min': 1234.9, 'max': 9467.18, 'dim': 2}, + {'type': 'discrete', 'items': ['a', 'bc', 'def', 'ghij']}, + {'type': 'int', 'min': 123, 'max': 234, 'dim': 1}, + ] + fidel_to_opt = [[9467.18, 9452.8], "def", [234]] + # Budget of evaluations + max_num_evals = 100 # Optimisation budget (max number of evaluations) + max_mf_capital = max_num_evals * mf_cost(fidel_to_opt) # Multi-fideltiy capital + + # First do the MF version + config_params = {'domain': domain_vars, 'fidel_space': fidel_vars, + 'fidel_to_opt': fidel_to_opt} + config = load_config(config_params) + # Optimise + mf_opt_pt, mf_opt_val, history = maximise_multifidelity_function(mf_objective, + config.fidel_space, config.domain, + config.fidel_to_opt, mf_cost, + max_mf_capital, config=config) + print(mf_opt_pt, mf_opt_val) + + # Non-MF version + config_params = {'domain': domain_vars} + config = load_config(config_params) + max_capital = 100 # Optimisation budget (max number of evaluations) + # Optimise + opt_pt, opt_val, history = maximise_function(objective, config.domain, + max_num_evals, config=config) + print(opt_pt, opt_val) + + +if __name__ == '__main__': + main() + From 1e6ae50ca794bbc4253924175b02e92dab280820 Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Sat, 30 Mar 2019 22:18:24 -0400 Subject: [PATCH 10/12] Add slight improvement to discrete euclidean code --- dragonfly/exd/cp_domain_utils.py | 20 +++++++++---- .../synthetic/discrete_euc/in_code_demo.py | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 examples/synthetic/discrete_euc/in_code_demo.py diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index 52695d0..b89f49a 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -133,6 +133,7 @@ def load_cp_domain_from_config_file(config_file, *args, **kwargs): def load_domain_from_params(domain_params, general_euclidean_kernel='', general_integral_kernel='', general_discrete_kernel='', general_discrete_numeric_kernel='', + general_discrete_euclidean_kernel='', domain_constraints=None, domain_info=None): """ Loads and creates a cartesian product object from a config_file. """ # pylint: disable=too-many-branches @@ -147,8 +148,8 @@ def load_domain_from_params(domain_params, general_discrete_idxs = [] general_discrete_numeric_items_list = [] general_discrete_numeric_idxs = [] - discrete_euclidean_items_list = [] - discrete_euclidean_idxs = [] + general_discrete_euclidean_items_list = [] + general_discrete_euclidean_idxs = [] raw_name_ordering = [] # We will need the following variables for the function caller and the kernel index_ordering = [] # keeps track of which index goes where in the domain @@ -158,7 +159,7 @@ def load_domain_from_params(domain_params, if param['type'] in ['float', 'int']: bound_dim = 1 if param['dim'] == '' else param['dim'] curr_bounds = [[param['min'], param['max']]] * bound_dim - elif param['type'] in ['discrete', 'discrete_numeric', 'boolean']: + elif param['type'] in ['discrete', 'discrete_numeric', 'boolean', 'discrete_euclidean']: items_dim = 1 if param['dim'] == '' else param['dim'] if param['type'] == 'boolean': param_items = [0, 1] @@ -175,8 +176,8 @@ def load_domain_from_params(domain_params, index_ordering.append(idx) elif param['type'] == 'discrete_euclidean': if param['kernel'] == '': - discrete_euclidean_items_list.extend(curr_items) - discrete_euclidean_idxs.extend(idx) + general_discrete_euclidean_items_list.append(curr_items) + general_discrete_euclidean_idxs.append(idx) else: list_of_domains.append(domains.DiscreteEuclideanDomain(curr_items)) index_ordering.append(idx) @@ -221,6 +222,15 @@ def load_domain_from_params(domain_params, dim_ordering.append(general_euclidean_dims) index_ordering.append(general_euclidean_idxs) kernel_ordering.append(general_euclidean_kernel) + if len(general_discrete_euclidean_items_list) > 0: + list_of_domains.extend([domains.DiscreteEuclideanDomain(x) for x in general_discrete_euclidean_items_list]) + general_discrete_euclidean_names = [domain_params[idx]['name'] for idx in + general_discrete_euclidean_idxs] + general_discrete_euclidean_dims = [domain_params[idx]['dim'] for idx in general_discrete_euclidean_idxs] + name_ordering.append(general_discrete_euclidean_names) + dim_ordering.append(general_discrete_euclidean_dims) + index_ordering.append(general_discrete_euclidean_idxs) + kernel_ordering.append(general_discrete_euclidean_kernel) if len(general_integral_bounds) > 0: list_of_domains.append(domains.IntegralDomain(general_integral_bounds)) general_integral_names = [domain_params[idx]['name'] for idx in general_integral_idxs] diff --git a/examples/synthetic/discrete_euc/in_code_demo.py b/examples/synthetic/discrete_euc/in_code_demo.py new file mode 100644 index 0000000..dc653ce --- /dev/null +++ b/examples/synthetic/discrete_euc/in_code_demo.py @@ -0,0 +1,30 @@ +""" + In code demo for discrete euclidean domains +""" +import numpy as np + +from dragonfly import load_config, maximise_function + + +def objective(x): + print(x) + return np.linalg.norm(x) + + +def main(): + size = 10 + dim = 3 + space = np.random.rand(size, dim) + domain_vars = [ + {'type': 'discrete_euclidean', 'items': space}, + ] + config_params = {'domain': domain_vars} + config = load_config(config_params) + max_num_evals = 100 + opt_pt, opt_val, history = maximise_function(objective, config.domain, max_num_evals, config=config) + print(opt_pt, opt_val) + + +if __name__ == '__main__': + main() + From d13f90abb238956c870bb4e9972394b304d91446 Mon Sep 17 00:00:00 2001 From: kirthevasank Date: Sun, 31 Mar 2019 22:25:55 -0400 Subject: [PATCH 11/12] A series of changes to get Discrete Euclidean domain working. Examples in examples/synthetic/disc_euclidean is now in working state. --- dragonfly/exd/cp_domain_utils.py | 43 +++++++++---------- dragonfly/exd/exd_core.py | 8 +--- dragonfly/gp/cartesian_product_gp.py | 13 +++--- dragonfly/opt/cp_ga_optimiser.py | 13 +++--- .../synthetic/discrete_euc/in_code_demo.py | 14 +++--- 5 files changed, 45 insertions(+), 46 deletions(-) diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index b89f49a..e3a4c78 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -18,9 +18,9 @@ get_original_order_from_reordered_list, \ transpose_list_of_lists from ..utils.oper_utils import random_sample_from_euclidean_domain, \ - random_sample_from_discrete_euclidean_domain, \ - random_sample_from_integral_domain, \ - random_sample_from_prod_discrete_domain + random_sample_from_discrete_euclidean_domain, \ + random_sample_from_integral_domain, \ + random_sample_from_prod_discrete_domain def _process_fidel_to_opt(raw_fidel_to_opt, fidel_space, fidel_space_orderings, @@ -133,7 +133,6 @@ def load_cp_domain_from_config_file(config_file, *args, **kwargs): def load_domain_from_params(domain_params, general_euclidean_kernel='', general_integral_kernel='', general_discrete_kernel='', general_discrete_numeric_kernel='', - general_discrete_euclidean_kernel='', domain_constraints=None, domain_info=None): """ Loads and creates a cartesian product object from a config_file. """ # pylint: disable=too-many-branches @@ -148,8 +147,6 @@ def load_domain_from_params(domain_params, general_discrete_idxs = [] general_discrete_numeric_items_list = [] general_discrete_numeric_idxs = [] - general_discrete_euclidean_items_list = [] - general_discrete_euclidean_idxs = [] raw_name_ordering = [] # We will need the following variables for the function caller and the kernel index_ordering = [] # keeps track of which index goes where in the domain @@ -159,7 +156,8 @@ def load_domain_from_params(domain_params, if param['type'] in ['float', 'int']: bound_dim = 1 if param['dim'] == '' else param['dim'] curr_bounds = [[param['min'], param['max']]] * bound_dim - elif param['type'] in ['discrete', 'discrete_numeric', 'boolean', 'discrete_euclidean']: + elif param['type'] in ['discrete', 'discrete_numeric', 'boolean', + 'discrete_euclidean']: items_dim = 1 if param['dim'] == '' else param['dim'] if param['type'] == 'boolean': param_items = [0, 1] @@ -174,13 +172,6 @@ def load_domain_from_params(domain_params, else: list_of_domains.append(domains.EuclideanDomain(curr_bounds)) index_ordering.append(idx) - elif param['type'] == 'discrete_euclidean': - if param['kernel'] == '': - general_discrete_euclidean_items_list.append(curr_items) - general_discrete_euclidean_idxs.append(idx) - else: - list_of_domains.append(domains.DiscreteEuclideanDomain(curr_items)) - index_ordering.append(idx) elif param['type'] == 'int': if param['kernel'] == '': general_integral_bounds.extend(curr_bounds) @@ -202,6 +193,10 @@ def load_domain_from_params(domain_params, else: list_of_domains.append(domains.ProdDiscreteNumericDomain(curr_items)) index_ordering.append(idx) + elif param['type'] == 'discrete_euclidean': + # We will treat the discrete Euclidean space differently + list_of_domains.append(domains.DiscreteEuclideanDomain(param_items)) + index_ordering.append(idx) elif param['type'].startswith(('nn', 'cnn', 'mlp')): from ..nn.nn_domains import get_nn_domain_from_constraints list_of_domains.append(get_nn_domain_from_constraints(param['type'], param)) @@ -222,15 +217,6 @@ def load_domain_from_params(domain_params, dim_ordering.append(general_euclidean_dims) index_ordering.append(general_euclidean_idxs) kernel_ordering.append(general_euclidean_kernel) - if len(general_discrete_euclidean_items_list) > 0: - list_of_domains.extend([domains.DiscreteEuclideanDomain(x) for x in general_discrete_euclidean_items_list]) - general_discrete_euclidean_names = [domain_params[idx]['name'] for idx in - general_discrete_euclidean_idxs] - general_discrete_euclidean_dims = [domain_params[idx]['dim'] for idx in general_discrete_euclidean_idxs] - name_ordering.append(general_discrete_euclidean_names) - dim_ordering.append(general_discrete_euclidean_dims) - index_ordering.append(general_discrete_euclidean_idxs) - kernel_ordering.append(general_discrete_euclidean_kernel) if len(general_integral_bounds) > 0: list_of_domains.append(domains.IntegralDomain(general_integral_bounds)) general_integral_names = [domain_params[idx]['name'] for idx in general_integral_idxs] @@ -258,6 +244,17 @@ def load_domain_from_params(domain_params, dim_ordering.append(general_discrete_numeric_dims) index_ordering.append(general_discrete_numeric_idxs) kernel_ordering.append(general_discrete_numeric_kernel) +# if len(general_discrete_euclidean_items_list) > 0: +# list_of_domains.extend([domains.DiscreteEuclideanDomain(x) for x in +# general_discrete_euclidean_items_list]) +# general_discrete_euclidean_names = [domain_params[idx]['name'] for idx in +# general_discrete_euclidean_idxs] +# general_discrete_euclidean_dims = [domain_params[idx]['dim'] for idx in +# general_discrete_euclidean_idxs] +# name_ordering.append(general_discrete_euclidean_names) +# dim_ordering.append(general_discrete_euclidean_dims) +# index_ordering.append(general_discrete_euclidean_idxs) +# kernel_ordering.append(general_discrete_euclidean_kernel) # Arrange all orderings into a namespace orderings = Namespace(index_ordering=index_ordering, kernel_ordering=kernel_ordering, diff --git a/dragonfly/exd/exd_core.py b/dragonfly/exd/exd_core.py index d03159d..cbbc2f1 100644 --- a/dragonfly/exd/exd_core.py +++ b/dragonfly/exd/exd_core.py @@ -206,14 +206,10 @@ def _get_curr_job_idxs_in_progress(self): dif = -1 if total_in_progress == 0 else max_idx - min_idx return '[min:%d, max:%d, dif:%d, tot:%d]'%(min_idx, max_idx, dif, total_in_progress) - def _get_multiple_workers_str(self): + @classmethod + def _get_multiple_workers_str(cls): """ Get string if there are multiple workers. """ return '' -# if self.worker_manager.num_workers == 1: -# return '' -# else: -# return 'jobs_by_each_worker=%s, in_progress=%s'%(self._get_jobs_for_each_worker(), -# self._get_curr_job_idxs_in_progress()) def _print_header(self): """ Print header. """ diff --git a/dragonfly/gp/cartesian_product_gp.py b/dragonfly/gp/cartesian_product_gp.py index d85f396..e9201b0 100644 --- a/dragonfly/gp/cartesian_product_gp.py +++ b/dragonfly/gp/cartesian_product_gp.py @@ -5,6 +5,7 @@ # pylint: disable=invalid-name # pylint: disable=abstract-method +# pylint: disable=no-member from __future__ import print_function from argparse import Namespace @@ -23,9 +24,6 @@ # Part 1: Parameters and Arguments # ================================ -# NOTE: The discrete euclidean domain will use the same parameters as the euclidean domain. -# TODO(kirthevasank) Add more information about this note. - # Domain kernel parameters _DFLT_DOMAIN_EUC_KERNEL_TYPE = 'matern' _DFLT_DOMAIN_INT_KERNEL_TYPE = 'matern' @@ -43,6 +41,8 @@ # Basic parameters basic_cart_product_gp_args = [ # For Euclidean domains ------------------------------------------------------------ + # These parameters will be shared by both Euclidean domains and Discrete Euclidean + # domains. get_option_specs('dom_euc_kernel_type', False, 'default', 'Kernel type for euclidean domains. '), get_option_specs('dom_euc_use_same_bandwidth', False, False, @@ -213,6 +213,7 @@ def __init__(self, X, Y, kernel, mean_func, noise_var, domain_lists_of_dists=Non build_posterior=True, reporter=None, handle_non_psd_kernels='project_first'): """ + Constructor X, Y: data kernel: a kernel object """ @@ -520,7 +521,8 @@ def _set_up_hyperparams_for_domain(fitter, X_data, gp_domain, dom_prefix, # Iterate through each individual domain and add it to the hyper parameters curr_dom_Xs = get_idxs_from_list_of_lists(X_data, dom_idx) # Some conditional options - if dom_type in ['euclidean', 'integral', 'prod_discrete_numeric']: + if dom_type in ['euclidean', 'integral', 'prod_discrete_numeric', + 'discrete_euclidean']: use_same_bw, matern_nu, esp_kernel_type, esp_matern_nu = \ _get_euc_int_options(dom_type, dom_prefix, fitter.options) # Now set things up depending on the kernel type @@ -828,7 +830,8 @@ def _build_kernel_for_domain(domain, dom_prefix, kernel_scale, gp_cts_hps, gp_ds kernel_type = _get_kernel_type_from_options(dom_type, 'dom', options) if kernel_type == 'default': kernel_type = get_default_kernel_type(dom.get_type()) - if dom_type in ['euclidean', 'integral', 'prod_discrete_numeric', 'discrete_euclidean']: + if dom_type in ['euclidean', 'integral', 'prod_discrete_numeric', + 'discrete_euclidean']: curr_kernel_hyperparams = _prep_kernel_hyperparams_for_euc_int_kernels( kernel_type, dom, dom_prefix, options) use_same_bw, _, esp_kernel_type, _ = \ diff --git a/dragonfly/opt/cp_ga_optimiser.py b/dragonfly/opt/cp_ga_optimiser.py index 9a3ff0e..c199d07 100644 --- a/dragonfly/opt/cp_ga_optimiser.py +++ b/dragonfly/opt/cp_ga_optimiser.py @@ -6,8 +6,9 @@ # pylint: disable=invalid-name -import numpy as np from copy import copy +import numpy as np +from scipy.spatial.distance import cdist # Local imports from ..exd.cp_domain_utils import get_processed_func_from_raw_func_for_cp_domain, \ load_cp_domain_from_config_file @@ -42,10 +43,10 @@ def euclidean_gauss_mutation(x, bounds, sigmas=None): ret = _get_gauss_perturbation(x, bounds, sigmas) return _return_ndarray_with_type(x, ret) -def discrete_euclidean_mutation(x, valid_vectors, uniform_prob=0.2): +def discrete_euclidean_mutation(x, list_of_items, uniform_prob=0.2): """ Makes a change depending on the vector values. """ # cdist requires 2d input - dists = cdist([x], valid_vectors)[0] + dists = cdist([x], list_of_items)[0] # Exponentiate and normalise to get the probabilities. unnorm_diff_probs = np.exp(-dists) sample_diff_probs = unnorm_diff_probs / unnorm_diff_probs.sum() @@ -56,7 +57,7 @@ def discrete_euclidean_mutation(x, valid_vectors, uniform_prob=0.2): # Now draw the samples idxs = np.arange(n) idx = np.random.choice(idxs, p=p) - ret = valid_vectors[idx] + ret = list_of_items[idx] return _return_ndarray_with_type(x, ret) def integral_gauss_mutation(x, bounds, sigmas=None): @@ -108,8 +109,6 @@ def get_default_mutation_op(dom): """ Returns the default mutation operator for the domain. """ if dom.get_type() == 'euclidean': return lambda x: euclidean_gauss_mutation(x, dom.bounds) - elif dom.get_type() == 'disc_euclidean': - return lambda x: discrete_euclidean_mutation(x, dom.valid_vectors) elif dom.get_type() == 'integral': return lambda x: integral_gauss_mutation(x, dom.bounds) elif dom.get_type() == 'discrete': @@ -120,6 +119,8 @@ def get_default_mutation_op(dom): return lambda x: discrete_numeric_exp_mutation(x, dom.list_of_items) elif dom.get_type() == 'prod_discrete_numeric': return lambda x: prod_discrete_numeric_exp_mutation(x, dom.list_of_list_of_items) + elif dom.get_type() == 'discrete_euclidean': + return lambda x: discrete_euclidean_mutation(x, dom.list_of_items) elif dom.get_type() == 'neural_network': from ..nn.nn_modifiers import get_single_nn_mutation_op return get_single_nn_mutation_op(dom, [0.5, 0.25, 0.125, 0.075, 0.05]) diff --git a/examples/synthetic/discrete_euc/in_code_demo.py b/examples/synthetic/discrete_euc/in_code_demo.py index dc653ce..c5b2a1b 100644 --- a/examples/synthetic/discrete_euc/in_code_demo.py +++ b/examples/synthetic/discrete_euc/in_code_demo.py @@ -1,27 +1,29 @@ """ In code demo for discrete euclidean domains """ -import numpy as np +import numpy as np from dragonfly import load_config, maximise_function def objective(x): - print(x) + """ Objective. """ return np.linalg.norm(x) def main(): + """ Main function. """ size = 10 dim = 3 - space = np.random.rand(size, dim) + disc_euc_items = list(np.random.random(size, dim)) domain_vars = [ - {'type': 'discrete_euclidean', 'items': space}, - ] + {'type': 'discrete_euclidean', 'items': disc_euc_items}, + ] config_params = {'domain': domain_vars} config = load_config(config_params) max_num_evals = 100 - opt_pt, opt_val, history = maximise_function(objective, config.domain, max_num_evals, config=config) + opt_pt, opt_val, history = maximise_function(objective, config.domain, max_num_evals, + config=config) print(opt_pt, opt_val) From 1eed185efe1c442520e1ad13ae9621b1df237957 Mon Sep 17 00:00:00 2001 From: kirthevasank Date: Sun, 31 Mar 2019 23:04:06 -0400 Subject: [PATCH 12/12] Fixed some bugs and added a second example. --- dragonfly/exd/cp_domain_utils.py | 11 ++--- .../{in_code_demo.py => in_code_demo_1.py} | 6 +-- .../synthetic/discrete_euc/in_code_demo_2.py | 48 +++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) rename examples/synthetic/discrete_euc/{in_code_demo.py => in_code_demo_1.py} (86%) create mode 100644 examples/synthetic/discrete_euc/in_code_demo_2.py diff --git a/dragonfly/exd/cp_domain_utils.py b/dragonfly/exd/cp_domain_utils.py index e3a4c78..27c522c 100644 --- a/dragonfly/exd/cp_domain_utils.py +++ b/dragonfly/exd/cp_domain_utils.py @@ -14,7 +14,7 @@ # Local imports from . import domains from ..parse.config_parser import config_parser -from ..utils.general_utils import flatten_list_of_objects_and_iterables, \ +from ..utils.general_utils import flatten_list_of_objects_and_lists, \ get_original_order_from_reordered_list, \ transpose_list_of_lists from ..utils.oper_utils import random_sample_from_euclidean_domain, \ @@ -60,7 +60,6 @@ def _preprocess_domain_parameters(domain_parameters, var_prefix='var_'): else: var_dict['min'] = var_dict['bounds'][0] var_dict['max'] = var_dict['bounds'][1] - print(domain_parameters) return domain_parameters @@ -301,13 +300,13 @@ def get_processed_point_from_raw_point(raw_x, cp_domain, index_ordering, dim_ord """ Obtains the processed point from the raw point. """ if not cp_domain.get_type() == 'cartesian_product': packed_x = [raw_x[index_ordering[j]] for j in index_ordering] - return flatten_list_of_objects_and_iterables(packed_x) + return flatten_list_of_objects_and_lists(packed_x) else: packed_x = [None] * len(index_ordering) for idx, idx_order in enumerate(index_ordering): if isinstance(idx_order, list): curr_elem = [raw_x[j] for j in idx_order] - curr_elem = flatten_list_of_objects_and_iterables(curr_elem) + curr_elem = flatten_list_of_objects_and_lists(curr_elem) packed_x[idx] = curr_elem elif dim_ordering[idx] == '' and (cp_domain.list_of_domains[idx].get_type() in \ ['euclidean', 'integral', 'prod_discrete', 'prod_discrete_numeric']): @@ -330,8 +329,8 @@ def get_raw_point_from_processed_point(proc_x, cp_domain, index_ordering, dim_or repacked_x.append(proc_x[idx]) else: repacked_x.append([proc_x[idx]]) - repacked_x = flatten_list_of_objects_and_iterables(repacked_x) - flattened_index_ordering = flatten_list_of_objects_and_iterables(index_ordering) + repacked_x = flatten_list_of_objects_and_lists(repacked_x) + flattened_index_ordering = flatten_list_of_objects_and_lists(index_ordering) x_orig_order = get_original_order_from_reordered_list(repacked_x, flattened_index_ordering) return x_orig_order diff --git a/examples/synthetic/discrete_euc/in_code_demo.py b/examples/synthetic/discrete_euc/in_code_demo_1.py similarity index 86% rename from examples/synthetic/discrete_euc/in_code_demo.py rename to examples/synthetic/discrete_euc/in_code_demo_1.py index c5b2a1b..2b27a68 100644 --- a/examples/synthetic/discrete_euc/in_code_demo.py +++ b/examples/synthetic/discrete_euc/in_code_demo_1.py @@ -8,14 +8,14 @@ def objective(x): """ Objective. """ - return np.linalg.norm(x) + return np.linalg.norm(x[0]) def main(): """ Main function. """ - size = 10 + size = 1000 dim = 3 - disc_euc_items = list(np.random.random(size, dim)) + disc_euc_items = list(np.random.random((size, dim))) domain_vars = [ {'type': 'discrete_euclidean', 'items': disc_euc_items}, ] diff --git a/examples/synthetic/discrete_euc/in_code_demo_2.py b/examples/synthetic/discrete_euc/in_code_demo_2.py new file mode 100644 index 0000000..5bf939a --- /dev/null +++ b/examples/synthetic/discrete_euc/in_code_demo_2.py @@ -0,0 +1,48 @@ +""" + In code demo for discrete euclidean domains +""" + +import numpy as np +from dragonfly import load_config, maximise_function + + +def hartmann6_3(x): + """ Hartmann function in 3D. """ + pt = np.array([x[1][1]/11.0, + x[0][0], + x[0][2], + x[0][1], + x[2]/114.0, + x[1][0]/11.0, + ]) + A = np.array([[ 10, 3, 17, 3.5, 1.7, 8], + [0.05, 10, 17, 0.1, 8, 14], + [ 3, 3.5, 1.7, 10, 17, 8], + [ 17, 8, 0.05, 10, 0.1, 14]], dtype=np.float64) + P = 1e-4 * 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]], dtype=np.float64) + log_sum_terms = (A * (P - pt)**2).sum(axis=1) + alpha = np.array([1.0, 1.2, 3.0, 3.2]) + return alpha.dot(np.exp(-log_sum_terms)) + + +def main(): + """ Main function. """ + disc_euc_items = list(np.random.random((1000, 3))) + domain_vars = [{'type': 'discrete_euclidean', 'items': disc_euc_items}, + {'type': 'float', 'min': 0, 'max': 11, 'dim': 2}, + {'type': 'int', 'min': 0, 'max': 114}, + ] + config_params = {'domain': domain_vars} + config = load_config(config_params) + max_num_evals = 100 + opt_pt, opt_val, history = maximise_function(hartmann6_3, config.domain, max_num_evals, + config=config) + print(opt_pt, opt_val) + + +if __name__ == '__main__': + main() +