11import numpy
22
3- population_fitness = numpy .array ([[20 , 2.2 ],
4- [60 , 4.4 ],
5- [65 , 3.5 ],
6- [15 , 4.4 ],
7- [55 , 4.5 ],
8- [50 , 1.8 ],
9- [80 , 4.0 ],
10- [25 , 4.6 ]])
3+ fitness = numpy .array ([[20 , 2.2 ],
4+ [60 , 4.4 ],
5+ [65 , 3.5 ],
6+ [15 , 4.4 ],
7+ [55 , 4.5 ],
8+ [50 , 1.8 ],
9+ [80 , 4.0 ],
10+ [25 , 4.6 ]])
11+
12+ # fitness = numpy.array([20,
13+ # 60,
14+ # 65,
15+ # 15,
16+ # 55,
17+ # 50,
18+ # 80,
19+ # 25])
20+
21+ # fitness = numpy.array([[20],
22+ # [60],
23+ # [65],
24+ # [15],
25+ # [55],
26+ # [50],
27+ # [80],
28+ # [25]])
1129
1230def get_non_dominated_set (curr_solutions ):
1331 """
@@ -66,13 +84,13 @@ def get_non_dominated_set(curr_solutions):
6684 # Return the dominated and non-dominated sets.
6785 return dominated_set , non_dominated_set
6886
69- def non_dominated_sorting (population_fitness ):
87+ def non_dominated_sorting (fitness ):
7088 """
71- Apply the non-dominant sorting over the population_fitness to create the pareto fronts based on non-dominaned sorting of the solutions.
89+ Apply the non-dominant sorting over the fitness to create the pareto fronts based on non-dominaned sorting of the solutions.
7290
7391 Parameters
7492 ----------
75- population_fitness : TYPE
93+ fitness : TYPE
7694 An array of the population fitness across all objective function.
7795
7896 Returns
@@ -87,15 +105,15 @@ def non_dominated_sorting(population_fitness):
87105 # The remaining set to be explored for non-dominance.
88106 # Initially it is set to the entire population.
89107 # The solutions of each non-dominated set are removed after each iteration.
90- remaining_set = population_fitness .copy ()
108+ remaining_set = fitness .copy ()
91109
92110 # Zipping the solution index with the solution's fitness.
93111 # This helps to easily identify the index of each solution.
94112 # Each element has:
95113 # 1) The index of the solution.
96114 # 2) An array of the fitness values of this solution across all objectives.
97- # remaining_set = numpy.array(list(zip(range(0, population_fitness .shape[0]), non_dominated_set)))
98- remaining_set = list (zip (range (0 , population_fitness .shape [0 ]), remaining_set ))
115+ # remaining_set = numpy.array(list(zip(range(0, fitness .shape[0]), non_dominated_set)))
116+ remaining_set = list (zip (range (0 , fitness .shape [0 ]), remaining_set ))
99117
100118 # A list mapping the index of each pareto front to the set of solutions in this front.
101119 solutions_fronts_indices = [- 1 ]* len (remaining_set )
@@ -116,14 +134,16 @@ def non_dominated_sorting(population_fitness):
116134
117135 return pareto_fronts , solutions_fronts_indices
118136
119- def crowding_distance (pareto_front ):
137+ def crowding_distance (pareto_front , fitness ):
120138 """
121139 Calculate the crowding dstance for all solutions in the current pareto front.
122140
123141 Parameters
124142 ----------
125143 pareto_front : TYPE
126144 The set of solutions in the current pareto front.
145+ fitness : TYPE
146+ The fitness of the current population.
127147
128148 Returns
129149 -------
@@ -164,8 +184,8 @@ def crowding_distance(pareto_front):
164184 obj_sorted = sorted (obj , key = lambda x : x [1 ])
165185
166186 # Get the minimum and maximum values for the current objective.
167- obj_min_val = min (population_fitness [:, obj_idx ])
168- obj_max_val = max (population_fitness [:, obj_idx ])
187+ obj_min_val = min (fitness [:, obj_idx ])
188+ obj_max_val = max (fitness [:, obj_idx ])
169189 denominator = obj_max_val - obj_min_val
170190 # To avoid division by zero, set the denominator to a tiny value.
171191 if denominator == 0 :
@@ -217,9 +237,11 @@ def crowding_distance(pareto_front):
217237 return obj_crowding_dist_list , crowding_dist_sum , crowding_dist_front_sorted_indices , crowding_dist_pop_sorted_indices
218238
219239def tournament_selection_nsga2 (self ,
220- pareto_fronts ,
221- solutions_fronts_indices ,
222- num_parents ):
240+ fitness ,
241+ num_parents
242+ # pareto_fronts,
243+ # solutions_fronts_indices,
244+ ):
223245
224246 """
225247 Select the parents using the tournament selection technique for NSGA-II.
@@ -231,9 +253,10 @@ def tournament_selection_nsga2(self,
231253 Later, the selected parents will mate to produce the offspring.
232254
233255 It accepts 2 parameters:
256+ -fitness: The fitness values for the current population.
257+ -num_parents: The number of parents to be selected.
234258 -pareto_fronts: A nested array of all the pareto fronts. Each front has its solutions.
235259 -solutions_fronts_indices: A list of the pareto front index of each solution in the current population.
236- -num_parents: The number of parents to be selected.
237260
238261 It returns an array of the selected parents alongside their indices in the population.
239262 """
@@ -246,6 +269,11 @@ def tournament_selection_nsga2(self,
246269 # The indices of the selected parents.
247270 parents_indices = []
248271
272+ # TODO If there is only a single objective, each pareto front is expected to have only 1 solution.
273+ # TODO Make a test to check for that behaviour.
274+ # Find the pareto fronts and the solutions' indicies in each front.
275+ pareto_fronts , solutions_fronts_indices = non_dominated_sorting (fitness )
276+
249277 # Randomly generate pairs of indices to apply for NSGA-II tournament selection for selecting the parents solutions.
250278 rand_indices = numpy .random .randint (low = 0.0 ,
251279 high = len (solutions_fronts_indices ),
@@ -284,7 +312,8 @@ def tournament_selection_nsga2(self,
284312 # Reaching here means the pareto front has more than 1 solution.
285313
286314 # Calculate the crowding distance of the solutions of the pareto front.
287- obj_crowding_distance_list , crowding_distance_sum , crowding_dist_front_sorted_indices , crowding_dist_pop_sorted_indices = crowding_distance (pareto_front .copy ())
315+ obj_crowding_distance_list , crowding_distance_sum , crowding_dist_front_sorted_indices , crowding_dist_pop_sorted_indices = crowding_distance (pareto_front = pareto_front .copy (),
316+ fitness = fitness )
288317
289318 # This list has the sorted front-based indices for the solutions in the current pareto front.
290319 crowding_dist_front_sorted_indices = list (crowding_dist_front_sorted_indices )
@@ -333,9 +362,11 @@ def tournament_selection_nsga2(self,
333362 return parents , numpy .array (parents_indices )
334363
335364def nsga2_selection (self ,
336- pareto_fronts ,
337- solutions_fronts_indices ,
338- num_parents ):
365+ fitness ,
366+ num_parents
367+ # pareto_fronts,
368+ # solutions_fronts_indices
369+ ):
339370
340371 """
341372 Select the parents using the Non-Dominated Sorting Genetic Algorithm II (NSGA-II).
@@ -348,9 +379,10 @@ def nsga2_selection(self,
348379 Later, the selected parents will mate to produce the offspring.
349380
350381 It accepts 2 parameters:
382+ -fitness: The fitness values for the current population.
383+ -num_parents: The number of parents to be selected.
351384 -pareto_fronts: A nested array of all the pareto fronts. Each front has its solutions.
352385 -solutions_fronts_indices: A list of the pareto front index of each solution in the current population.
353- -num_parents: The number of parents to be selected.
354386
355387 It returns an array of the selected parents alongside their indices in the population.
356388 """
@@ -363,6 +395,11 @@ def nsga2_selection(self,
363395 # The indices of the selected parents.
364396 parents_indices = []
365397
398+ # TODO If there is only a single objective, each pareto front is expected to have only 1 solution.
399+ # TODO Make a test to check for that behaviour.
400+ # Find the pareto fronts and the solutions' indicies in each front.
401+ pareto_fronts , solutions_fronts_indices = non_dominated_sorting (fitness )
402+
366403 # The number of remaining parents to be selected.
367404 num_remaining_parents = num_parents
368405
@@ -387,7 +424,8 @@ def nsga2_selection(self,
387424 # If only a subset of the front is needed, then use the crowding distance to sort the solutions and select only the number needed.
388425
389426 # Calculate the crowding distance of the solutions of the pareto front.
390- obj_crowding_distance_list , crowding_distance_sum , crowding_dist_front_sorted_indices , crowding_dist_pop_sorted_indices = crowding_distance (current_pareto_front .copy ())
427+ obj_crowding_distance_list , crowding_distance_sum , crowding_dist_front_sorted_indices , crowding_dist_pop_sorted_indices = crowding_distance (pareto_front = current_pareto_front .copy (),
428+ fitness = fitness )
391429
392430 for selected_solution_idx in crowding_dist_pop_sorted_indices [0 :num_remaining_parents ]:
393431 # Insert the parent into the parents array.
@@ -404,8 +442,10 @@ def nsga2_selection(self,
404442 # Make sure the parents indices is returned as a NumPy array.
405443 return parents , numpy .array (parents_indices )
406444
407-
408- pareto_fronts , solutions_fronts_indices = non_dominated_sorting (population_fitness )
445+ # TODO If there is only a single objective, each pareto front is expected to have only 1 solution.
446+ # TODO Make a test to check for that behaviour.
447+ # Find the pareto fronts and the solutions' indicies in each front.
448+ pareto_fronts , solutions_fronts_indices = non_dominated_sorting (fitness )
409449# # print('\nsolutions_fronts_indices\n', solutions_fronts_indices)
410450# for i, s in enumerate(pareto_fronts):
411451# # print(f'Dominated Pareto Front Set {i+1}:\n{s}')
@@ -422,27 +462,33 @@ class Object(object):
422462obj .K_tournament = 2
423463
424464parents , parents_indices = tournament_selection_nsga2 (self = obj ,
425- pareto_fronts = pareto_fronts ,
426- solutions_fronts_indices = solutions_fronts_indices ,
427- num_parents = 40 )
465+ fitness = fitness ,
466+ num_parents = 4
467+ # pareto_fronts=pareto_fronts,
468+ # solutions_fronts_indices=solutions_fronts_indices,
469+ )
428470print (f'Tournament Parent Selection for NSGA-II - Indices: \n { parents_indices } ' )
429471
430472parents , parents_indices = nsga2_selection (self = obj ,
431- pareto_fronts = pareto_fronts ,
432- solutions_fronts_indices = solutions_fronts_indices ,
433- num_parents = 40 )
473+ fitness = fitness ,
474+ num_parents = 4
475+ # pareto_fronts=pareto_fronts,
476+ # solutions_fronts_indices=solutions_fronts_indices,
477+ )
434478print (f'NSGA-II Parent Selection - Indices: \n { parents_indices } ' )
435479
436480# for idx in range(len(pareto_fronts)):
437481# # Fetch the current pareto front.
438482# pareto_front = pareto_fronts[idx]
439- # obj_crowding_distance_list, crowding_distance_sum, crowding_dist_front_sorted_indices, crowding_dist_pop_sorted_indices = crowding_distance(pareto_front.copy())
483+ # obj_crowding_distance_list, crowding_distance_sum, crowding_dist_front_sorted_indices, crowding_dist_pop_sorted_indices = crowding_distance(pareto_front=pareto_front.copy(),
484+ # fitness=fitness)
440485# print('Front IDX', crowding_dist_front_sorted_indices)
441486# print('POP IDX ', crowding_dist_pop_sorted_indices)
442487# print(f'Sorted Sum of Crowd Dists\n{crowding_distance_sum}')
443488
444489# # Fetch the current pareto front.
445490# pareto_front = pareto_fronts[0]
446- # obj_crowding_distance_list, crowding_distance_sum, crowding_dist_front_sorted_indices, crowding_dist_pop_sorted_indices = crowding_distance(pareto_front.copy())
491+ # obj_crowding_distance_list, crowding_distance_sum, crowding_dist_front_sorted_indices, crowding_dist_pop_sorted_indices = crowding_distance(pareto_front=pareto_front.copy(),
492+ # fitness=fitness)
447493# print('\n', crowding_dist_pop_sorted_indices)
448494# print(f'Sorted Sum of Crowd Dists\n{crowding_distance_sum}')
0 commit comments