3939 get_interval ,
4040)
4141
42- supported_neighbor_methods = ["strictly-adjacent" , "adjacent" , "Hamming" , "closest-param-indices" ]
42+
43+ supported_neighbor_methods = ["strictly-adjacent" , "adjacent" , "Hamming" , "Hamming-adjacent" , "closest-param-indices" ]
4344
4445
4546class Searchspace :
@@ -65,6 +66,7 @@ def __init__(
6566 strictly-adjacent: differs +1 or -1 parameter index value for each parameter
6667 adjacent: picks closest parameter value in both directions for each parameter
6768 Hamming: any parameter config with 1 different parameter value is a neighbor
69+ Hamming-adjacent: differs by closest parameter value for exactly 1 parameter.
6870 Optionally sort the searchspace by the order in which the parameter values were specified. By default, sort goes from first to last parameter, to reverse this use sort_last_param_first.
6971 Optionally an imported cache can be used instead with `from_cache`, in which case the `tune_params`, `restrictions` and `max_threads` arguments can be set to None, and construction is skipped.
7072 Optionally construction can be deffered to a later time by setting `defer_construction` to True, in which case the searchspace is not built on instantiation (experimental).
@@ -1038,6 +1040,44 @@ def __get_random_neighbor_hamming(self, param_config: tuple) -> tuple:
10381040 return self .get_param_configs_at_indices ([i ])[0 ]
10391041 return None
10401042
1043+ def __get_neighbors_indices_hammingadjacent (self , param_config_index : int = None , param_config : tuple = None ) -> List [int ]:
1044+ """Get the neighbors using adjacent distance from the parameter configuration (parameter index absolute difference >= 1)."""
1045+ param_config_value_indices = (
1046+ self .get_param_indices (param_config )
1047+ if param_config_index is None
1048+ else self .params_values_indices [param_config_index ]
1049+ )
1050+
1051+ # compute boolean mask for all configuration that differ at exactly one parameter (Hamming distance == 1)
1052+ hamming_mask = np .count_nonzero (self .params_values_indices != param_config_value_indices , axis = 1 ) == 1
1053+
1054+ # get the configuration indices of the hamming neighbors
1055+ hamming_indices , = np .nonzero (hamming_mask )
1056+
1057+ # for the hamming neighbors, calculate the difference between parameter value indices
1058+ hamming_values_indices = self .params_values_indices [hamming_mask ]
1059+
1060+ # for each parameter get the closest upper and lower parameter (absolute index difference >= 1)
1061+ upper_bound = np .min (
1062+ hamming_values_indices ,
1063+ initial = self .get_list_param_indices_numpy_max (),
1064+ axis = 0 ,
1065+ where = hamming_values_indices > param_config_value_indices ,
1066+ )
1067+
1068+ lower_bound = np .max (
1069+ hamming_values_indices ,
1070+ initial = self .get_list_param_indices_numpy_min (),
1071+ axis = 0 ,
1072+ where = hamming_values_indices < param_config_value_indices ,
1073+ )
1074+
1075+ # return mask for adjacent neighbors (each parameter is within bounds)
1076+ adjacent_mask = np .all ((lower_bound <= hamming_values_indices ) & (hamming_values_indices <= upper_bound ), axis = 1 )
1077+
1078+ # return hamming neighbors that are also adjacent
1079+ return hamming_indices [adjacent_mask ]
1080+
10411081 def __get_random_neighbor_adjacent (self , param_config : tuple ) -> tuple :
10421082 """Get an approximately random adjacent neighbor of the parameter configuration."""
10431083 # NOTE: this is not truly random as we only progressively increase the allowed index difference if no neighbors are found, but much faster than generating all neighbors
@@ -1051,6 +1091,7 @@ def __get_random_neighbor_adjacent(self, param_config: tuple) -> tuple:
10511091 if param_config_index is None
10521092 else self .params_values_indices [param_config_index ]
10531093 )
1094+
10541095 max_index_difference_per_param = [max (len (self .params_values [p ]) - 1 - i , i ) for p , i in enumerate (param_config_value_indices )]
10551096
10561097 # calculate the absolute difference between the parameter value indices
@@ -1079,6 +1120,7 @@ def __get_random_neighbor_adjacent(self, param_config: tuple) -> tuple:
10791120 allowed_index_difference += 1
10801121 return None
10811122
1123+
10821124 def __add_to_neighbor_partial_cache (self , param_config : tuple , neighbor_indices : List [int ], neighbor_method : str , full_neighbors = False ):
10831125 """Add the neighbor indices to the partial cache using the given parameter configuration."""
10841126 param_config_index = self .get_param_config_index (param_config )
@@ -1164,6 +1206,13 @@ def __build_neighbors_index(self, neighbor_method) -> List[List[int]]:
11641206 # for each parameter configuration, find the neighboring parameter configurations
11651207 if self .params_values_indices is None :
11661208 self .__prepare_neighbors_index ()
1209+
1210+ if neighbor_method == "Hamming-adjacent" :
1211+ return list (
1212+ self .__get_neighbors_indices_hammingadjacent (param_config_index , param_config )
1213+ for param_config_index , param_config in enumerate (self .list )
1214+ )
1215+
11671216 if neighbor_method == "strictly-adjacent" :
11681217 return list (
11691218 self .__get_neighbors_indices_strictlyadjacent (param_config_index , param_config )
@@ -1348,6 +1397,8 @@ def get_neighbors_indices_no_cache(self, param_config: tuple, neighbor_method=No
13481397 self .__prepare_neighbors_index ()
13491398
13501399 # if the passed param_config is fictious, we can not use the pre-calculated neighbors index
1400+ if neighbor_method == "Hamming-adjacent" :
1401+ return self .__get_neighbors_indices_hammingadjacent (param_config_index , param_config )
13511402 if neighbor_method == "strictly-adjacent" :
13521403 return self .__get_neighbors_indices_strictlyadjacent (param_config_index , param_config )
13531404 if neighbor_method == "adjacent" :
0 commit comments