1+ """
2+ Adaptive Tabu-Guided Grey Wolf Optimization.
3+
4+ Algorithm generated as part of the paper "Automated Algorithm Design For Auto-Tuning Optimizers".
5+ """
6+
7+ import random
8+ import math
9+ from collections import deque
10+
11+ from kernel_tuner .util import StopCriterionReached
12+ from kernel_tuner .strategies import common
13+ from kernel_tuner .strategies .common import CostFunc
14+
15+
16+ _options = dict (
17+ budget = ("maximum number of evaluations" , 5000 ),
18+ pack_size = ("number of wolves" , 8 ),
19+ tabu_factor = ("tabu size multiplier" , 3 ),
20+ shake_rate = ("base shaking probability" , 0.2 ),
21+ jump_rate = ("random jump probability" , 0.15 ),
22+ stagn_limit = ("stagnation limit before restart" , 80 ),
23+ restart_ratio = ("fraction of pack to restart" , 0.3 ),
24+ t0 = ("initial temperature" , 1.0 ),
25+ t_decay = ("temperature decay rate" , 5.0 ),
26+ t_min = ("minimum temperature" , 1e-4 ),
27+ constraint_aware = ("constraint-aware optimization (True/False)" , True ),
28+ )
29+
30+
31+ def tune (searchspace , runner , tuning_options ):
32+
33+ options = tuning_options .strategy_options
34+ if "x0" in options :
35+ raise ValueError ("Starting point (x0) is not supported for AdaptiveTabuGreyWolf strategy." )
36+ (budget , pack_size , tabu_factor , shake_rate , jump_rate ,
37+ stagn_limit , restart_ratio , t0 , t_decay , t_min , constraint_aware ) = \
38+ common .get_options (options , _options )
39+
40+ cost_func = CostFunc (searchspace , tuning_options , runner )
41+
42+ alg = AdaptiveTabuGreyWolf (
43+ searchspace , cost_func ,
44+ budget , pack_size , tabu_factor ,
45+ shake_rate , jump_rate ,
46+ stagn_limit , restart_ratio ,
47+ t0 , t_decay , t_min ,
48+ constraint_aware ,
49+ tuning_options .verbose ,
50+ )
51+
52+ try :
53+ alg .run ()
54+ except StopCriterionReached as e :
55+ if tuning_options .verbose :
56+ print (e )
57+
58+ return cost_func .results
59+
60+
61+ tune .__doc__ = common .get_strategy_docstring ("Adaptive Tabu Grey Wolf" , _options )
62+
63+
64+ class AdaptiveTabuGreyWolf :
65+
66+ def __init__ (self , searchspace , cost_func ,
67+ budget , pack_size , tabu_factor ,
68+ shake_rate , jump_rate ,
69+ stagn_limit , restart_ratio ,
70+ t0 , t_decay , t_min ,
71+ constraint_aware , verbose ):
72+
73+ self .searchspace = searchspace
74+ self .cost_func = cost_func
75+ self .budget = budget
76+ self .pack_size = pack_size
77+ self .tabu = deque (maxlen = pack_size * tabu_factor )
78+ self .shake_rate = shake_rate
79+ self .jump_rate = jump_rate
80+ self .stagn_limit = stagn_limit
81+ self .restart_ratio = restart_ratio
82+ self .t0 = t0
83+ self .t_decay = t_decay
84+ self .t_min = t_min
85+ self .constraint_aware = constraint_aware
86+ self .verbose = verbose
87+
88+ def evaluate (self , dna ):
89+ return self .cost_func (dna , check_restrictions = not self .constraint_aware )
90+
91+ def sample_valid (self ):
92+ while True :
93+ x = list (self .searchspace .get_random_sample (1 )[0 ])
94+ if not self .constraint_aware or self .searchspace .is_param_config_valid (tuple (x )):
95+ return x
96+
97+ def repair (self , sol ):
98+ if not self .constraint_aware or self .searchspace .is_param_config_valid (tuple (sol )):
99+ return sol
100+
101+ # try neighbors
102+ for m in ("adjacent" , "Hamming" , "strictly-adjacent" ):
103+ for nb in self .searchspace .get_neighbors (tuple (sol ), neighbor_method = m ):
104+ if self .searchspace .is_param_config_valid (nb ):
105+ return list (nb )
106+
107+ return self .sample_valid ()
108+
109+ def run (self ):
110+
111+ # initialize pack
112+ pack = []
113+ num_evals = 0
114+
115+ for cfg in self .searchspace .get_random_sample (self .pack_size ):
116+ sol = list (cfg )
117+
118+ try :
119+ val = self .evaluate (sol )
120+ num_evals += 1
121+ except StopCriterionReached :
122+ raise
123+
124+ pack .append ((sol , val ))
125+ self .tabu .append (tuple (sol ))
126+
127+ pack .sort (key = lambda x : x [1 ])
128+
129+ best_sol , best_val = pack [0 ]
130+ stagn = 0
131+ iteration = 0
132+
133+ while num_evals < self .budget :
134+
135+ iteration += 1
136+ frac = num_evals / self .budget
137+
138+ # temperature schedule
139+ T = max (self .t_min , self .t0 * math .exp (- self .t_decay * frac ))
140+
141+ # reheating
142+ if stagn and stagn % max (1 , (self .stagn_limit // 2 )) == 0 :
143+ T += self .t0 * 0.2
144+
145+ # adaptive shaking
146+ shake_p = min (0.5 , self .shake_rate * (1 + stagn / self .stagn_limit ))
147+
148+ pack .sort (key = lambda x : x [1 ])
149+ alpha , beta , delta = pack [0 ][0 ], pack [1 ][0 ], pack [2 ][0 ]
150+
151+ new_pack = []
152+
153+ for sol , sol_val in pack :
154+
155+ # leaders survive
156+ if sol in (alpha , beta , delta ):
157+ new_pack .append ((sol , sol_val ))
158+ continue
159+
160+ D = len (sol )
161+
162+ # recombination
163+ child = [
164+ random .choice ((alpha [i ], beta [i ], delta [i ], sol [i ]))
165+ for i in range (D )
166+ ]
167+
168+ # shaking
169+ if random .random () < shake_p :
170+ if random .random () < self .jump_rate :
171+ idx = random .randrange (D )
172+ rnd = random .choice (self .searchspace .get_random_sample (1 ))
173+ child [idx ] = rnd [idx ]
174+ else :
175+ method = "adjacent" if frac < 0.5 else "strictly-adjacent"
176+ nbrs = list (self .searchspace .get_neighbors (tuple (child ), neighbor_method = method ))
177+ if nbrs :
178+ child = list (random .choice (nbrs ))
179+
180+ # repair
181+ child = self .repair (child )
182+ tchild = tuple (child )
183+
184+ # tabu handling
185+ if tchild in self .tabu :
186+ nbrs = list (self .searchspace .get_neighbors (tchild , neighbor_method = "Hamming" ))
187+ if nbrs :
188+ child = list (random .choice (nbrs ))
189+
190+ try :
191+ fch = self .evaluate (child )
192+ num_evals += 1
193+ except StopCriterionReached :
194+ raise
195+
196+ self .tabu .append (tuple (child ))
197+
198+ # SA acceptance
199+ dE = fch - sol_val
200+ if dE <= 0 or random .random () < math .exp (- dE / T ):
201+ new_pack .append ((child , fch ))
202+ else :
203+ new_pack .append ((sol , sol_val ))
204+
205+ if num_evals >= self .budget :
206+ break
207+
208+ pack = new_pack
209+ pack .sort (key = lambda x : x [1 ])
210+
211+ # update best
212+ if pack [0 ][1 ] < best_val :
213+ best_sol , best_val = pack [0 ]
214+ stagn = 0
215+ else :
216+ stagn += 1
217+
218+ # restart
219+ if stagn >= self .stagn_limit :
220+ nr = int (math .ceil (self .pack_size * self .restart_ratio ))
221+
222+ for i in range (self .pack_size - nr , self .pack_size ):
223+ sol = self .sample_valid ()
224+
225+ try :
226+ val = self .evaluate (sol )
227+ num_evals += 1
228+ except StopCriterionReached :
229+ raise
230+
231+ pack [i ] = (sol , val )
232+ self .tabu .append (tuple (sol ))
233+
234+ pack .sort (key = lambda x : x [1 ])
235+ best_sol , best_val = pack [0 ]
236+ stagn = 0
237+
238+ if self .verbose and num_evals % 50 == 0 :
239+ print (f"Evaluations: { num_evals } , best: { best_val } " )
0 commit comments