-
Notifications
You must be signed in to change notification settings - Fork 5
/
stoch_optim_utilities.py
631 lines (582 loc) · 24.6 KB
/
stoch_optim_utilities.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
import numpy as np
import random as rnd
'''
Utilities for Stochastic Optimization Algorithms
'''
def LS_f_v3(f, p_init, max_iter, bounds, radius, reduce_iter, reduce_frac):
'''
---------------------
LOCAL SEARCH ALGORITHM
---------------------
--- input ---
f: (function) Objective function
p_init: (array) Initial point (where the funtion is going to be evaluated)
max_iter: (integer) Maximum number of iterations
bounds: (list) Bounds on the search domain
radius: (float) Initial search radius
reduce_iter: (integer) Number of iterations with the same optimum that will induce a search radius reduction
reduce_frac: (float) Fraction to which the search radius is reduced. Must be between 0 and 1
--- output ---
best_value: (float) The best value found with the iteration using the best_position
best_position: (array) The best position in which the final best_value was evaluated
--- Notes ---
1. f stands for "fast implementation" which means it does not compile results
'''
#Initialization
f = f
p_init = p_init
max_iter = max_iter
bounds = bounds
radius = radius
reduce_iter = reduce_iter
reduce_frac = reduce_frac
# ----------------------------------------------
best_position = p_init
best_value = f(p_init)
dim = len(p_init)
fail_count = 0
# Iteration Loop
for i_iter in range(max_iter):
# Tries random values in the vecinity of the best position so far
# Assure that every variable is within the bounds
check = False
while not check:
temp_bound = np.array([rnd.uniform(bounds[i][0],bounds[i][1]) for i in range(dim)])
p_trial = - best_position + radius * temp_bound
check = check_bounds(bounds, p_trial)
if not check:
p_trial = best_position - radius * temp_bound
check = check_bounds(bounds, p_trial)
# If the modification of the complete set did not work. It will modify each variable individually
if not check:
p_trial = check_bounds_variable(bounds, p_trial, radius)
check = True
# If trial value is better than best value, this gets substituted
trial_value = f(p_trial)
if trial_value < best_value:
best_position = p_trial
best_value = trial_value
else:
fail_count += 1
# Check whether it's time to set radius to smaller one. Resets failcount
if fail_count == reduce_iter:
radius *= reduce_frac
fail_count = 0
return best_value, best_position
def LS_p_v3(f, p_init, max_iter, bounds, radius, reduce_iter, reduce_frac):
'''
---------------------
LOCAL SEARCH ALGORITHM
---------------------
--- input ---
f: (function) Objective function
p_init: (array) Initial point (where the funtion is going to be evaluated)
max_iter: (integer) Maximum number of iterations
bounds: (list) Bounds on the search domain
radius: (float) Initial search radius
reduce_iter: (integer) Number of iterations with the same optimum that will induce a search radius reduction
reduce_frac: (float) Fraction to which the search radius is reduced. Must be between 0 and 1
--- output ---
best_value: (float) The best value found with the iteration using the best_position
best_position: (array) The best position in which the final best_value was evaluated
trajectory: (matrix) Column 0: Number of iteration. Column 1: Value for current iteration
trajectory_x: (matrix) Positions visited during the iterations
--- Notes ---
1. The "p" states for the "plot" version of the algorithm. It outputs all the iteration trajectory
'''
#Initialization
f = f
p_init = p_init
max_iter = max_iter
bounds = bounds
radius = radius
reduce_iter = reduce_iter
reduce_frac = reduce_frac
# ----------------------------------------------
best_position = p_init
best_value = f(p_init)
dim = len(p_init)
fail_count = 0
trajectory = np.zeros((max_iter + 1, 2))
trajectory[0][0] = 0
trajectory[0][1] = best_value
trajectory_x = np.zeros((max_iter + 1, dim))
trajectory_x[0] = best_position
# Iteration Loop
for i_iter in range(max_iter):
# Tries random values in the vecinity of the best position so far
# Assure that every variable is within the bounds
check = False
while not check:
temp_bound = np.array([rnd.uniform(bounds[i][0],bounds[i][1]) for i in range(dim)])
p_trial = - best_position + radius * temp_bound
check = check_bounds(bounds, p_trial)
if not check:
p_trial = best_position - radius * temp_bound
check = check_bounds(bounds, p_trial)
# If the modification of the complete set did not work. It will modify each variable individually
if not check:
p_trial = check_bounds_variable(bounds, p_trial, radius)
check = True
# If trial value is better than best value, this gets substituted
trial_value = f(p_trial)
if trial_value < best_value:
best_position = p_trial
best_value = trial_value
else:
fail_count += 1
# Check whether it's time to set radius to smaller one. Resets failcount
if fail_count == reduce_iter:
radius *= reduce_frac
fail_count = 0
# Stores trajectory
trajectory[i_iter + 1][0] = i_iter + 1
trajectory[i_iter + 1][1] = best_value
trajectory_x[i_iter + 1] = best_position
return best_value, best_position, trajectory, trajectory_x
def RS_f_v2(f, p_best, max_iter, bounds):
'''
---------------------
RANDOM SEARCH ALGORITHM
---------------------
--- input ---
f: objective function
p_best: hot-start a best point
max_iter: maximum number of iterations
bounds: bounds on the search domain
--- output ---
best_value: (float) The best value found with the iteration using the best_position
best_position: (array) The best position in which the final best_value was evaluated
--- Notes ---
1. p_best is used in case a good value is already known
2. The "f" states for the "fast" version of the algorithm. It only outputs the best values found
'''
# Initialization
f = f
p_best = p_best
max_iter = max_iter
bounds = bounds
# ----------------------------------------------
best_position = p_best
best_value = f(p_best)
dim = len(p_best)
# Search loop
for i_iter in range(max_iter):
# Tries random values
p_trial = np.array([rnd.uniform(bounds[i][0],bounds[i][1]) for i in range(dim)])
trial_value = f(p_trial)
# If trial values is better than best position, this gets substituted
if trial_value < best_value:
best_position = p_trial
best_value = trial_value
return best_value, best_position
def RS_p_v2(f, p_best, max_iter, bounds):
'''
---------------------
RANDOM SEARCH ALGORTHM
---------------------
--- input ---
f: objective function
p_best: hot-start a best point
max_iter: maximum number of iterations
bounds: bounds on the search domain
--- output ---
best_value: (float) The best value found with the iteration using the best_position
best_position: (array) The best position in which the final best_value was evaluated
trajectory: (matrix) Column 0: Number of iteration. Column 1: Value for current iteration
--- Notes ---
1. p_best is used in case a good value is already known.
2. The "p" states for the "plot" version of the algorithm. It outputs all the iteration trajectory
'''
# Initialization
f = f
p_best = p_best
max_iter = max_iter
bounds = bounds
# ----------------------------------------------
best_position = p_best
best_value = f(p_best)
dim = len(p_best)
# Creating arrays for the plots
all_results = np.zeros((max_iter,2))
# Search loop
for i_iter in range(max_iter):
# Tries random values
p_trial = np.array([rnd.uniform(bounds[i][0],bounds[i][1]) for i in range(dim)])
trial_value = f(p_trial)
# If trial values is better than best position, this gets substituted
if trial_value < best_value:
best_position = np.copy(p_trial)
best_value = trial_value
# Compiling results
all_results[i_iter][0] = i_iter
all_results[i_iter][1] = best_value
return best_value, best_position, all_results
def check_bounds_variable(bounds, position, radius):
'''
------------------------------
CHECK BOUNDS VARIABLE BY VARIABLE AND ASSURES THEY ARE WITHIN BOUNDS CHANGING THEM WHEN NECESSARY
------------------------------
--- input ---
bounds: (list) Bounds on the search domain
position: (array) Proposed current position of the particle
--- output ---
position: (array) Corrected array to be within bounds in each variable
'''
# Initialization
bounds = bounds
position = position
radius = radius
# ----------------------------------------------
check = False
while not check:
check_var_count = 0 #To count variables which are within bounds
for variable in range(len(position)):
bounds_variable = [bounds[variable]] # Extracts the bounds for the specific variable
check_variable = check_bounds(bounds_variable, np.array([position[variable]]))
if not check_variable:
r1 = variable - radius # Left limit radius
r2 = variable + radius # Right limit radius
if r2 < bounds_variable[0][0]: # O /------/
position[variable] = bounds_variable[0][0]
elif r1 > bounds_variable[0][1]: # /------/ O
position[variable] = bounds_variable[0][1]
elif r2 > bounds_variable[0][0] and r1 < bounds_variable[0][0]: # O----/
position[variable] = rnd.uniform(bounds_variable[0][0], r2)
elif r1 < bounds_variable[0][1] and r2 > bounds_variable[0][1]: # /----O
position[variable] = rnd.uniform(r1, bounds_variable[0][1])
elif r1 > bounds_variable[0][0] and r2 < bounds_variable[0][1]: # /--O--/
position[variable] = rnd.uniform(r1, r2)
check_variable = check_bounds(bounds_variable, np.array([position[variable]]))
if check_variable:
check_var_count += 1
if check_var_count == len(position):
check = True
if check:
return position
def check_bounds(bounds, position):
'''
------------------------------
CHECK BOUNDS ALGORITM
------------------------------
--- input ---
bounds: (list) Bounds on the search domain
position: (array) Proposed current position of the particle
--- output ---
valid_position: (boolean) "True" if position is within the allowed bounds in every dimension and "False" otherwise
'''
# Initialization
bounds = bounds
position = position
# ----------------------------------------------
dim = len(bounds)
count = 0
for i in range(dim):
if position[i] <= bounds[i][1] and position[i] >= bounds[i][0]:
count += 1
if count == dim:
return True
else:
return False
def tabu_zone(tabu, continuos_radius):
'''
------------------------------
DEFINES TABU ZONE FOR EACH VARIABLE IN A POINT GIVEN A CONTINUOS RADIUS
------------------------------
--- input ---
tabu: (array) Point classified as Tabu
continuos_radius: (list) Radius around each variable to define tabu zone for each variable in the tabu point
--- output ---
tabu_z: (list) It contains the tabu zone per each variable of the point
'''
# Initialization
tabu = tabu
continuos_radius
# ----------------------------------------------
tabu_z = [] # To store tabu zones for each variable
# Defines tabu zone per each variable
for i in range(len(tabu)):
left = tabu[i] - continuos_radius[i] # Defines left bound of the tabu zone
right = tabu[i] + continuos_radius[i] # Defines right bound of the tabu zone
tabu_z.append((left, right))
return tabu_z
def tabu_zones(tabuList, continuos_radius):
'''
------------------------------
GIVES TABU ZONES FOR EACH TABU IN THE LIST
------------------------------
--- input ---
tabuList: (list) Stores all the current tabu points
continuos_radius: (list) Radius around each variable to define tabu zone for each variable in the tabu point
--- output ---
tabu_zs: (list) It contains the tabu zones per each tabu point
'''
# Initialization
tabuList = tabuList
continuos_radius = continuos_radius
# ----------------------------------------------
tabu_zs = []
for tabu in tabuList:
t_z = tabu_zone(tabu, continuos_radius)
tabu_zs.append(t_z)
return tabu_zs
def check_tabu(position, tabu_zs):
'''
------------------------------
CHECKS IF A POINT IS A TABU OR NOT
------------------------------
--- input ---
position: (array) Position within the search space
tabu_zs: (list) It contains the tabu zones per each tabu point
--- output ---
tabu: (boolean) "True" if position is a tabu (it's within the tabu zone in each one of its variables) and "False" otherwise
'''
# Initialization
position = position
tabu_zs = tabu_zs
# ----------------------------------------------
for tabu_z in tabu_zs:
check = check_bounds(tabu_z, position) # Checks if the position is within a tabu zone
if check:
return True
if not check:
return False
def p_outside_tabu(bounds, tabu_zs):
'''
------------------------------
GENENRATES A POSITION OUTSIDE TABU ZONES
------------------------------
--- input ---
bounds: (list) Bounds on the search domain
tabu_zs: (list) It contains the tabu zones per each tabu point
--- output ---
p_out_tabu: (array) Random particle outside tabu zones and within bounds
'''
# Initialization
bounds = bounds
tabu_zs = tabu_zs
# ----------------------------------------------
p_out_tabu = np.zeros(len(bounds)) # Creates an array of zeros to store new position
check = True
while check:
select_tabu_z = rnd.choice(tabu_zs) # Selects randomnly one of the sets of tabu zones
for i in range(len(bounds)):
# Selects valid limits
if select_tabu_z[i][0] > bounds[i][0] and select_tabu_z[i][1] < bounds[i][1]: # /--O--/
left1 = bounds[i][0]
right1 = select_tabu_z[i][0]
left2 = select_tabu_z[i][1]
right2 = bounds[i][1]
elif select_tabu_z[i][0] > bounds[i][0] and select_tabu_z[i][1] >= bounds[i][1]: # /----O
left1 = bounds[i][0]
right1 = select_tabu_z[i][0]
left2 = left1
right2 = right1
elif select_tabu_z[i][0] <= bounds[i][0] and select_tabu_z[i][1] < bounds[i][1]: # O----/
left1 = select_tabu_z[i][1]
right1 = bounds[i][1]
left2 = left1
right2 = right1
new_bounds = rnd.choice(((left1, right1),(left2, right2)))
p_out_tabu[i] = rnd.uniform(new_bounds[0], new_bounds[1])
check = check_tabu(p_out_tabu, tabu_zs) # Checks whether the new position is a tabu or not
return p_out_tabu
def new_particle(bounds):
'''
------------------------------
GENERATES NEW PARTICLE (POINT) RANDOMLY
------------------------------
--- input ---
bounds: (list) Bounds on the search domain
--- output ---
particle: (array) Random particle within bounds
'''
#Initialization
B = bounds
# ----------------------------------------------
dim = len(B)
#Generate new random particle within the bounds
particle = np.array([rnd.uniform(B[i][0],B[i][1]) for i in range(dim)])
return particle
def first_generation(num_p, bounds):
'''
------------------------------
GENERATES FIRST GENERATION FOR GA
------------------------------
--- input ---
num_p: (integer) Number of particles in the new generation to be created
bounds: (list) Bounds on the search domain
--- output ---
generation: (list) Set of new particles
'''
# Initialization
S = num_p
B = bounds
# ----------------------------------------------
generation = []
# Generates a set of num_p new particles
for point in range(S):
particle = new_particle(B)
generation.append(particle)
return generation
def sort_standard(f, generation):
'''
------------------------------
STANDARD SORT (WALKS THROUGH EACH ELEMENT IN LIST)
------------------------------
--- input ---
f: (function) Objetive function
generation: (list) Set of new particles
--- output ---
g_sorted: (matrix) Sorted set of new particles. Row: particle, Column: variable
'''
# Initialization
F = f
G = generation
# ----------------------------------------------
dim = len(G)
num_var = len(G[0])
g_sorted = np.reshape([(0.0) for i in range(num_var*dim)], (dim,num_var)) # Creates a matrix of zeros
values = np.zeros((dim,2)) # Creates a matrix of zeros
# Stores the points with their respective value(as a key)
index = 0
for particle in G:
values[index][0] = F(particle)
values[index][1] = index
index += 1
# Sorts values
values_sorted = values[np.argsort(values[:,0])]
# Stores sorted values in the previously created matrix
ind_sorted = values_sorted[:,1]
i = 0
for ind in ind_sorted:
g_sorted[i] = G[int(ind)]
i += 1
return g_sorted
def selection(g_sorted, best_num, random_num):
'''
------------------------------
SELECTION OF THE FITTEST POINTS AND SOME RANDOME ONES
------------------------------
--- input ---
g_sorted: (matrix) Sorted set of new particles. Row: particle, Column: variable.
best_num: (integer) Number of best particles you want to select
random_num: (integer) Number of random particles you want to select from the rest
--- output ---
selected: (matrix) Set of particles selected
'''
#Initialization
g = g_sorted
best = best_num
random = random_num
# ----------------------------------------------
num_var = len(g[0])
selected = np.reshape([(0.0) for i in range(num_var*(best + random))], ((best + random),num_var)) # Creates a matrix of zeros
# Stores the best points to the matrix "selected"
for i in range(best):
selected[i] = g[i]
# Stores points from the rest of the generation randomly
for i in range(random):
selected[i + best] = rnd.choice(g[best:])
return selected
def define_parents(selected, parents_child):
'''
------------------------------
SELECTION OF POINTS WHICH ARE GONNA BE RECOMBINED AMONG THEM
------------------------------
--- input ---
selected: (matrix) Set of particles selected
parents_child: (integer) Number of parents per child
--- output ---
groups_par: (list) Set of groups of parents that are gonna be recombined
'''
# Initialization
parents = selected
N = parents_child
# ----------------------------------------------
groups_par = []
# Loop to define parents
row_parent = 0 # Number of the row in the Matrix for the current parent in the next loop
for parent in parents:
group_repro = np.zeros((N,len(parent))) # Creates a matrix of zeros
# Makes a matrix of the candidates to be reproduced with
candidates = np.delete(parents, row_parent, 0)
# Randomly select the parents from candidates to be later reproduce with.
for i in range (N-1):
cand = rnd.choice(candidates)
group_repro[i] = parent
group_repro[i+1] = cand
index_row = np.where([cand] == [cand])[0][0] # Gives de row number of "cand"
candidates = np.delete(candidates, index_row, 0) # Prevent repetition within the group of parents
groups_par.append(group_repro)
row_parent += 1
return groups_par
def reproduction(groups_par, num_children):
'''
------------------------------
REPRODUCTION OF PARENTS BY RANDOM RECOMBINATION
------------------------------
--- input ---
groups_par: (list) Set of groups of parents that are gonna be recombined
num_children: (integer) Number of children per group of parents
--- output ---
new_gener_r: (matrix) Set of children (points) produced by recombination of the groups of parents
'''
# Initialization
groups = groups_par
num_ch = num_children
# ----------------------------------------------
new_gener = []
num_ac_child = 0 #Number of current children per group of parents
# Produces the number of children specified per group of parents
while num_ac_child < num_ch:
# Loops per group of parents
for group in groups:
child = []
#Loops per each variable in a point
for variable in range(len(group[0])):
can_var = []
# Selects the variable randomly between the group of parents
for i in range(len(group)):
can_var.append(group[i][variable])
child.append(rnd.choice(can_var))
new_gener.append(child)
num_ac_child += 1
new_gener_r = np.asarray(new_gener)
return new_gener_r
def mutation(new_gener_r, bounds, continuos_radius):
'''
------------------------------
MUTATION OF NEW CHILDREN WITH CERTAIN PROBABILITY
------------------------------
--- input ---
new_gener_r: (matrix) Set of children (points) produced by recombination of the groups of parents
bounds: (list) Bounds on the search domain
continuos_radius: (list) Radius around each variable to define prohibeted zone for each variable. Continuos numbers application
--- output ---
g_m: (matrix) Set of children (points) passed through random mutation with certain probability
'''
#Initialization
g_r = new_gener_r
B = bounds
c_r = continuos_radius
# ----------------------------------------------
num_var = len(g_r[0])
# Makes a random change in a random variable per child with certain probability of mutation
i = 0
for child in g_r:
probability = 1/num_var # 1/num of decision variables according to Deb,K. (2001), Multi-Objective Optimization Using Evolutionary Algorithms
if child in np.delete(g_r, i): # Prevents a child to be exactly the same as one of its parents
probability = 1
if rnd.random() < probability:
random_index = np.where( child == (rnd.choice(child)))[0][0]
c_r_v = [c_r[random_index]] # Selects the continuos radius to the chosen variable to be mutated and store it as need it for "tabu_zones"
var_mut = [np.array([child[random_index]])] # Selects the chosen variable to be mutated and store it as need it for "tabu_zones"
proh_zone = tabu_zones(var_mut, c_r_v) # Defines the zone within the continuos radius
bounds_var = [B[random_index]] # Selects the bounds for the chosen variable to be mutated
mutated_variable = p_outside_tabu(bounds_var, proh_zone) # Gives a random number for the chosen variable outside continuos radius and within bounds
child[random_index] = mutated_variable # Replaces the mutated variable in place
#child[random_index] = rnd.uniform(B[random_index][0],B[random_index][1])
i += 1
new_gener_m = g_r
return new_gener_m