1818
1919class PatternSearchDisplay (SingleObjectiveDisplay ):
2020
21+ def __init__ (self , ** kwargs ):
22+ super ().__init__ (favg = False , ** kwargs )
23+
2124 def _do (self , problem , evaluator , algorithm ):
2225 super ()._do (problem , evaluator , algorithm )
23- self .output .append ("T " , algorithm .T )
26+ self .output .append ("delta " , np . max ( np . abs ( algorithm .explr_delta )) )
2427
2528
2629class PatternSearchTermination (Termination ):
2730
28- def __init__ (self , min_T = 1e-5 , ** kwargs ):
31+ def __init__ (self , eps = 1e-5 , ** kwargs ):
2932 super ().__init__ ()
3033 self .default = SingleObjectiveDefaultTermination (** kwargs )
31- self .min_T = min_T
34+ self .eps = eps
3235
3336 def do_continue (self , algorithm ):
3437 decision_default = self .default .do_continue (algorithm )
35- if algorithm .T < self .min_T :
38+ delta = np .max (np .abs (algorithm .explr_delta ))
39+ if delta < self .eps :
3640 return decision_default
3741 else :
3842 return True
@@ -41,61 +45,89 @@ def do_continue(self, algorithm):
4145class PatternSearch (LocalSearch ):
4246
4347 def __init__ (self ,
44- T = 0.1 ,
45- a = 2 ,
48+ explr_delta = 0.25 ,
49+ explr_rho = 0.5 ,
50+ pattern_step = 2 ,
51+ eps = 1e-5 ,
4652 display = PatternSearchDisplay (),
4753 ** kwargs ):
54+
4855 super ().__init__ (display = display , ** kwargs )
49- self .T = T
50- self .a = a
51- self .default_termination = PatternSearchTermination (x_tol = 1e-6 , f_tol = 1e-6 , nth_gen = 1 , n_last = 2 )
56+ self .explr_rho = explr_rho
57+ self .pattern_step = pattern_step
58+ self .explr_delta = explr_delta
59+ self .default_termination = PatternSearchTermination (eps = eps , x_tol = 1e-6 , f_tol = 1e-6 , nth_gen = 1 , n_last = 2 )
5260
53- def _next (self ):
54- # the current best solution found so far
55- best = self .opt [0 ]
61+ def _initialize (self , ** kwargs ):
62+ super ()._initialize (** kwargs )
5663
57- # first do the exploration move
58- opt = self ._exploration_move (best )
64+ # make delta a vector - the sign is later updated individually
65+ if not isinstance (self .explr_delta , np .ndarray ):
66+ self .explr_delta = np .ones (self .problem .n_var ) * self .explr_delta
5967
60- # if the exploration move could not improve the current solution
61- if opt == best :
62- self .T = self .T / 2
68+ def _next (self ):
6369
64- # if the move brought up a new solution -> perform a line search
65- else :
66- self ._pattern_move ( best , opt )
70+ # in the beginning of each iteration first do an exploration move
71+ self . _previous = self . opt [ 0 ]
72+ self . _current = self ._exploration_move ( self . _previous )
6773
68- def _pattern_move ( self , old_best , new_best ):
69- _current , _next = old_best , new_best
74+ # one iteration is the combination of this two moves repeatedly until delta needs to be reduced
75+ while self . _previous != self . _current :
7076
71- while True :
72- X = _current .X + self .a * (_next .X - _current .X )
73- xl , xu = self .problem .bounds ()
74- X = repair_out_of_bounds_manually (X , xl , xu )
75- tentative = Individual (X = X )
77+ # use the pattern move to get a new trial vector
78+ trial = self ._pattern_move (self ._previous , self ._current )
7679
77- self . evaluator . eval ( self . problem , tentative , algorithm = self )
78- self . pop = Population . merge ( self .pop , tentative )
80+ # perform an exploration move around the trial vector - the best known solution is always stored in _current
81+ explr = self ._exploration_move ( trial , opt = self . _current )
7982
80- # if the tentative could not further improve the old best
81- if not is_better (tentative , _next ):
83+ if not is_better (explr , self ._current ):
8284 break
83- else :
84- # if we have improved
85- _current , _next = _next , tentative
8685
87- def _exploration_move (self , opt ):
88- xl , xu = self .problem .bounds ()
86+ self ._previous , self ._current = self ._current , explr
87+
88+ self .explr_delta *= self .explr_rho
89+
90+ def _pattern_move (self , _current , _next ):
91+
92+ # get the direction and assign the corresponding delta value
93+ direction = (_next .X - _current .X )
94+
95+ # get the delta sign adjusted
96+ sign = np .sign (direction )
97+ sign [sign == 0 ] = - 1
98+ self .explr_delta = sign * np .abs (self .explr_delta )
8999
90- def step (x , sign ):
91- # copy to not modify the original value
100+ # calculate the new X and repair out of bounds if necessary
101+ X = _current .X + self .pattern_step * direction
102+ repair_out_of_bounds_manually (X , * self .problem .bounds ())
103+
104+ # create the new center individual without evaluating it
105+ trial = Individual (X = X )
106+
107+ return trial
108+
109+ def _exploration_move (self , center , opt = None ):
110+ if opt is None :
111+ opt = center
112+
113+ def step (x , delta , k ):
114+
115+ # copy and add delta to the new point
92116 X = np .copy (x )
93117
94- # add the value in the normalized space to the k-th component
95- X [ k ] = X [k ] + ( sign * self . T ) * ( xu [ k ] - xl [ k ])
118+ # normalize the delta by the bounds if they are provided by the problem
119+ eps = delta [k ]
96120
97- # repair if out of bounds
98- X = repair_out_of_bounds_manually (X , xl , xu )
121+ # if the problem has bounds normalize the delta
122+ if self .problem .has_bounds ():
123+ xl , xu = self .problem .bounds ()
124+ eps *= (xu [k ] - xl [k ])
125+
126+ # now add to the current solution
127+ X [k ] = X [k ] + eps
128+
129+ # repair if out of bounds if necessary
130+ X = repair_out_of_bounds_manually (X , * self .problem .bounds ())
99131
100132 # return the new solution as individual
101133 mutant = pop_from_array_or_individual (X )[0 ]
@@ -104,26 +136,26 @@ def step(x, sign):
104136
105137 for k in range (self .problem .n_var ):
106138
107- # randomly assign + or - as a sign
108- sign = 1 if np .random .random () < 0.5 else - 1
109-
110139 # create the the individual and evaluate it
111- mutant = step (opt .X , sign )
140+ mutant = step (center .X , self . explr_delta , k )
112141 self .evaluator .eval (self .problem , mutant , algorithm = self )
113142 self .pop = Population .merge (self .pop , mutant )
114143
115144 if is_better (mutant , opt ):
116- opt = mutant
145+ center , opt = mutant , mutant
117146
118147 else :
119148
149+ # inverse the sign of the delta
150+ self .explr_delta [k ] = - self .explr_delta [k ]
151+
120152 # now try the other sign if there was no improvement
121- mutant = step (opt .X , - 1 * sign )
153+ mutant = step (center .X , self . explr_delta , k )
122154 self .evaluator .eval (self .problem , mutant , algorithm = self )
123155 self .pop = Population .merge (self .pop , mutant )
124156
125157 if is_better (mutant , opt ):
126- opt = mutant
158+ center , opt = mutant , mutant
127159
128160 return opt
129161
0 commit comments