@@ -103,6 +103,8 @@ def __init__(
103103 self .param_names = list (self .tune_params .keys ())
104104 self .params_values = tuple (tuple (param_vals ) for param_vals in self .tune_params .values ())
105105 self .params_values_indices = None
106+ self ._alloc_diff = None
107+ self ._alloc_sum_of_index_differences = None
106108 self .build_neighbors_index = build_neighbors_index
107109 self .solver_method = solver_method
108110 self .tune_param_is_numeric = { param_name : all (isinstance (val , (int , float )) for val in param_values ) and not any (isinstance (val , bool ) for val in param_values ) for (param_name , param_values ) in tune_params .items () }
@@ -715,21 +717,46 @@ def get_list_param_indices_numpy(self) -> np.ndarray:
715717 the NumPy array.
716718 """
717719 if self .__list_param_indices is None :
720+
721+ # compute the lookups
718722 tune_params_to_index_lookup = list ()
719723 tune_params_from_index_lookup = list ()
724+ all_values_integer_nonnegative = True
720725 for param_name , param_values in self .tune_params .items ():
721726 tune_params_to_index_lookup .append ({ value : index for index , value in enumerate (param_values ) })
722727 tune_params_from_index_lookup .append ({ index : value for index , value in enumerate (param_values ) })
723-
728+ if (all_values_integer_nonnegative and
729+ not all (isinstance (v , int ) and 0 < v < 2 ** 15 for v in param_values )
730+ ):
731+ all_values_integer_nonnegative = False
732+
724733 # build the list
725- list_param_indices = list ()
726- for param_config in self .list :
727- list_param_indices .append ([tune_params_to_index_lookup [index ][val ] for index , val in enumerate (param_config )])
734+ if all_values_integer_nonnegative :
735+ # optimized case for integer non-negative values
736+ configs = np .asarray (self .list )
737+ index_arrays = []
738+ for values in self .tune_params .values ():
739+ arr = np .full (max (values ) + 1 , - 1 , dtype = np .int16 )
740+ for i , v in enumerate (values ):
741+ arr [v ] = i
742+ index_arrays .append (arr )
743+ # use advanced indexing to build the list of parameter indices
744+ list_param_indices = np .column_stack ([
745+ index_arrays [i ][configs [:, i ]]
746+ for i in range (configs .shape [1 ])
747+ ])
748+ else :
749+ # general case for any type of values
750+ list_param_indices = list ()
751+ for param_config in self .list :
752+ list_param_indices .append ([tune_params_to_index_lookup [index ][val ] for index , val in enumerate (param_config )])
753+ list_param_indices = np .array (list_param_indices )
728754
729755 # register the computed results
730756 self .__tune_params_to_index_lookup = tune_params_to_index_lookup
731757 self .__tune_params_from_index_lookup = tune_params_from_index_lookup
732- self .__list_param_indices = np .array (list_param_indices )
758+ self .__list_param_indices = list_param_indices
759+
733760 assert self .__list_param_indices .shape == (self .size , self .num_params ), f"Expected shape { (self .size , self .num_params )} , got { self .__list_param_indices .shape } "
734761
735762 # calculate the actual minimum and maximum index for each parameter after restrictions
@@ -962,6 +989,8 @@ def __prepare_neighbors_index(self):
962989 """Prepare by calculating the indices for the individual parameters."""
963990 if self .params_values_indices is None :
964991 self .params_values_indices = self .get_list_param_indices_numpy ()
992+ self ._alloc_diff = np .empty_like (self .params_values_indices , dtype = self .params_values_indices .dtype )
993+ self ._alloc_sum_of_index_differences = np .empty ((self .params_values_indices .shape [0 ],), dtype = self .params_values_indices .dtype )
965994
966995 def __get_neighbor_indices_closest_param_indices (self , param_config : tuple , param_index : int = None , return_one = False ) -> List [int ]:
967996 """Get the neighbors closest in parameter indices difference from the parameter configuration. Always returns at least 1 neighbor."""
@@ -972,19 +1001,19 @@ def __get_neighbor_indices_closest_param_indices(self, param_config: tuple, para
9721001 self .__prepare_neighbors_index ()
9731002
9741003 # calculate the absolute difference between the parameter value indices
975- abs_index_difference = np .abs (self .params_values_indices - np .array (param_indices ), dtype = self .params_values_indices .dtype )
976- # calculate the sum of the absolute differences for each parameter configuration
977- sum_of_index_differences = np .sum (abs_index_difference , axis = 1 )
1004+ self .__calc_sum_of_index_differences (np .array (param_indices ))
9781005 if param_index is not None :
9791006 # set the sum of index differences to infinity for the parameter index to avoid returning the same parameter configuration
980- sum_of_index_differences [param_index ] = self .get_list_param_indices_numpy_max ()
1007+ self ._alloc_sum_of_index_differences [param_index ] = self .get_list_param_indices_numpy_max ()
1008+
1009+ # return the indices of the closest parameter configurations
9811010 if return_one :
9821011 # if return_one is True, return the index of the closest parameter configuration (faster than finding all)
983- get_partial_neighbors_indices = [np .argmin (sum_of_index_differences )]
1012+ matching_indices = [np .argmin (self . _alloc_sum_of_index_differences ). item ( )]
9841013 else :
9851014 # find the param config indices where the difference is the smallest
986- min_difference = np .min (sum_of_index_differences )
987- matching_indices = (sum_of_index_differences == min_difference ).nonzero ()[0 ]
1015+ min_difference = np .min (self . _alloc_sum_of_index_differences )
1016+ matching_indices = (self . _alloc_sum_of_index_differences == min_difference ).nonzero ()[0 ]
9881017 return matching_indices
9891018
9901019 def __get_neighbors_indices_hamming (self , param_config : tuple ) -> List [int ]:
@@ -1073,15 +1102,17 @@ def __get_neighbors_indices_strictlyadjacent(
10731102 """Get the neighbors using strictly adjacent distance from the parameter configuration (parameter index absolute difference == 1)."""
10741103 if self .params_values_indices is None :
10751104 self .__prepare_neighbors_index ()
1076- param_config_value_indices = (
1105+ param_config_value_indices = np . array (
10771106 self .get_param_indices (param_config )
10781107 if param_config_index is None
10791108 else self .params_values_indices [param_config_index ]
10801109 )
1110+
10811111 # calculate the absolute difference between the parameter value indices
10821112 abs_index_difference = np .abs (self .params_values_indices - param_config_value_indices , dtype = self .params_values_indices .dtype )
10831113 # get the param config indices where the difference is one or less for each position
10841114 matching_indices = (np .max (abs_index_difference , axis = 1 ) <= 1 ).nonzero ()[0 ]
1115+
10851116 # as the selected param config does not differ anywhere, remove it from the matches
10861117 if param_config_index is not None :
10871118 matching_indices = np .setdiff1d (matching_indices , [param_config_index ], assume_unique = True )
@@ -1145,12 +1176,18 @@ def __build_neighbors_index(self, neighbor_method) -> List[List[int]]:
11451176 )
11461177 if neighbor_method == "closest-param-indices" :
11471178 return list (
1148- self .__get_neighbor_indices_closest_param_indices (param_config , param_config_index )
1179+ self .__get_neighbor_indices_closest_param_indices (param_config , param_config_index , return_one = False )
11491180 for param_config_index , param_config in enumerate (self .list )
11501181 )
11511182
11521183 raise NotImplementedError (f"The neighbor method { neighbor_method } is not implemented" )
11531184
1185+ def __calc_sum_of_index_differences (self , target_param_config_indices : np .ndarray ):
1186+ """Calculates the absolute difference between the parameter value indices and `target_param_config_indices` into `self._alloc_sum_of_index_differences`."""
1187+ np .subtract (self .params_values_indices , target_param_config_indices , out = self ._alloc_diff )
1188+ np .abs (self ._alloc_diff , out = self ._alloc_diff )
1189+ np .einsum ('ij->i' , self ._alloc_diff , out = self ._alloc_sum_of_index_differences )
1190+
11541191 def get_random_sample_indices (self , num_samples : int ) -> np .ndarray :
11551192 """Get the list indices for a random, non-conflicting sample."""
11561193 if num_samples > self .size :
@@ -1169,7 +1206,7 @@ def get_random_sample(self, num_samples: int) -> List[tuple]:
11691206 return self .get_param_configs_at_indices (self .get_random_sample_indices (num_samples ))
11701207
11711208 def get_distributed_random_sample_indices (self , num_samples : int , sampling_factor = 10 ) -> List [int ]:
1172- """Get a distributed random sample of parameter configuration indices. Note: `get_LHS_random_sample_indices ` is likely faster and better distributed."""
1209+ """Get a distributed random sample of parameter configuration indices. Note: `get_LHS_sample_indices ` is likely faster and better distributed."""
11731210 if num_samples > self .size :
11741211 warn (
11751212 f"Too many samples requested ({ num_samples } ), reducing the number of samples to half of the searchspace size ({ self .size } )"
@@ -1219,16 +1256,12 @@ def get_next_sample(lower: tuple, upper: tuple) -> tuple:
12191256 self .__prepare_neighbors_index ()
12201257 target_sample_indices = list ()
12211258 for target_sample_param_config_indices in target_samples_param_indices :
1222- # calculate the absolute difference between the parameter value indices
1223- abs_index_difference = np .abs (self .params_values_indices - target_sample_param_config_indices , dtype = self .params_values_indices .dtype )
1224- # find the param config index where the difference is the smallest
1225- sum_of_index_differences = np .sum (abs_index_difference , axis = 1 )
12261259 param_index = self .get_param_config_index (self .get_param_config_from_param_indices (target_sample_param_config_indices ))
12271260 if param_index is not None :
1228- # set the sum of index differences to infinity for the parameter index to avoid returning the same parameter configuration
1229- sum_of_index_differences [ param_index ] = self . get_list_param_indices_numpy_max ()
1230- min_index_difference_index = np . argmin ( sum_of_index_differences )
1231- target_sample_indices .append (min_index_difference_index .item ())
1261+ target_sample_indices . append ( param_index )
1262+ else :
1263+ self . __calc_sum_of_index_differences ( target_sample_param_config_indices )
1264+ target_sample_indices .append (np . argmin ( self . _alloc_sum_of_index_differences ) .item ())
12321265
12331266 # filter out duplicate samples and replace with random ones
12341267 target_sample_indices = list (set (target_sample_indices ))
@@ -1267,16 +1300,12 @@ def get_LHS_sample_indices(self, num_samples: int) -> List[int]:
12671300 # for each of the target sample indices, calculate which parameter configuration is closest
12681301 target_sample_indices = list ()
12691302 for target_sample_param_config_indices in target_samples_param_indices :
1270- # calculate the absolute difference between the parameter value indices
1271- abs_index_difference = np .abs (self .params_values_indices - target_sample_param_config_indices , dtype = self .params_values_indices .dtype )
1272- # find the param config index where the difference is the smallest
1273- sum_of_index_differences = np .sum (abs_index_difference , axis = 1 )
12741303 param_index = self .get_param_config_index (self .get_param_config_from_param_indices (target_sample_param_config_indices ))
12751304 if param_index is not None :
1276- # set the sum of index differences to infinity for the parameter index to avoid returning the same parameter configuration
1277- sum_of_index_differences [ param_index ] = self . get_list_param_indices_numpy_max ()
1278- min_index_difference_index = np . argmin ( sum_of_index_differences )
1279- target_sample_indices .append (min_index_difference_index .item ())
1305+ target_sample_indices . append ( param_index )
1306+ else :
1307+ self . __calc_sum_of_index_differences ( target_sample_param_config_indices )
1308+ target_sample_indices .append (np . argmin ( self . _alloc_sum_of_index_differences ) .item ())
12801309
12811310 # filter out duplicate samples and replace with random ones
12821311 target_sample_indices = list (set (target_sample_indices ))
0 commit comments