1
1
import numpy
2
2
3
- population = 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
- def non_dominant_sorting (curr_set ):
13
- # List of the members of the current dominant front/set.
14
- dominant_set = []
15
- # List of the non-members of the current dominant front/set.
16
- non_dominant_set = []
17
- for idx1 , sol1 in enumerate (curr_set ):
18
- # Flag indicates whether the solution is a member of the current dominant set.
19
- is_dominant = True
20
- for idx2 , sol2 in enumerate (curr_set ):
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 ]])
11
+
12
+ def get_non_dominated_set (curr_solutions ):
13
+ """
14
+ Get the set of non-dominated solutions from the current set of solutions.
15
+
16
+ Parameters
17
+ ----------
18
+ curr_solutions : TYPE
19
+ The set of solutions to find its non-dominated set.
20
+
21
+ Returns
22
+ -------
23
+ dominated_set : TYPE
24
+ A set of the dominated solutions.
25
+ non_dominated_set : TYPE
26
+ A set of the non-dominated set.
27
+
28
+ """
29
+ # List of the members of the current dominated pareto front/set.
30
+ dominated_set = []
31
+ # List of the non-members of the current dominated pareto front/set.
32
+ non_dominated_set = []
33
+ for idx1 , sol1 in enumerate (curr_solutions ):
34
+ # Flag indicates whether the solution is a member of the current dominated set.
35
+ is_dominated = True
36
+ for idx2 , sol2 in enumerate (curr_solutions ):
21
37
if idx1 == idx2 :
22
38
continue
23
39
# Zipping the 2 solutions so the corresponding genes are in the same list.
24
40
# The returned array is of size (N, 2) where N is the number of genes.
25
- b = numpy .array (list (zip (sol1 , sol2 )))
26
-
41
+ two_solutions = numpy .array (list (zip (sol1 [ 1 ] , sol2 [ 1 ] )))
42
+
27
43
#TODO Consider repacing < by > for maximization problems.
28
44
# Checking for if any solution dominates the current solution by applying the 2 conditions.
29
- # le_eq: All elements must be True.
30
- # le: Only 1 element must be True.
31
- le_eq = b [:, 1 ] <= b [:, 0 ]
32
- le = b [:, 1 ] < b [:, 0 ]
45
+ # le_eq (less than or equal) : All elements must be True.
46
+ # le (less than) : Only 1 element must be True.
47
+ le_eq = two_solutions [:, 1 ] <= two_solutions [:, 0 ]
48
+ le = two_solutions [:, 1 ] < two_solutions [:, 0 ]
33
49
34
50
# If the 2 conditions hold, then a solution dominates the current solution.
35
- # The current solution is not considered a member of the dominant set.
51
+ # The current solution is not considered a member of the dominated set.
36
52
if le_eq .all () and le .any ():
37
- # print(f"{sol2} dominates {sol1}")
38
- # Set the is_dominant flag to False to not insert the current solution in the current dominant set.
39
- # Instead, insert it into the non-dominant set.
40
- is_dominant = False
41
- non_dominant_set .append (sol1 )
53
+ # Set the is_dominated flag to False to NOT insert the current solution in the current dominated set.
54
+ # Instead, insert it into the non-dominated set.
55
+ is_dominated = False
56
+ non_dominated_set .append (sol1 )
42
57
break
43
58
else :
44
- # Reaching here means the solution does not dominant the current solution.
45
- # print(f"{sol2} does not dominate {sol1}")
59
+ # Reaching here means the solution does not dominate the current solution.
46
60
pass
47
61
48
62
# If the flag is True, then no solution dominates the current solution.
49
- if is_dominant :
50
- dominant_set .append (sol1 )
63
+ if is_dominated :
64
+ dominated_set .append (sol1 )
65
+
66
+ # Return the dominated and non-dominated sets.
67
+ return dominated_set , non_dominated_set
68
+
69
+ def non_dominated_sorting (population_fitness ):
70
+ """
71
+ Apply the non-dominant sorting over the population_fitness to create sets of non-dominaned solutions.
72
+
73
+ Parameters
74
+ ----------
75
+ population_fitness : TYPE
76
+ An array of the population fitness across all objective function.
51
77
52
- # Return the dominant and non-dominant sets.
53
- return dominant_set , non_dominant_set
78
+ Returns
79
+ -------
80
+ non_dominated_sets : TYPE
81
+ An array of the non-dominated sets.
54
82
55
- dominant_set = []
56
- non_dominant_set = population .copy ()
57
- while len (non_dominant_set ) > 0 :
58
- d1 , non_dominant_set = non_dominant_sorting (non_dominant_set )
59
- dominant_set .append (numpy .array (d1 ))
83
+ """
84
+ # A list of all non-dominated sets.
85
+ non_dominated_sets = []
60
86
61
- for i , s in enumerate (dominant_set ):
62
- print (f'Dominant Front Set { i + 1 } :\n { s } ' )
87
+ # The remaining set to be explored for non-dominance.
88
+ # Initially it is set to the entire population.
89
+ # The solutions of each non-dominated set are removed after each iteration.
90
+ remaining_set = population_fitness .copy ()
91
+
92
+ # Zipping the solution index with the solution's fitness.
93
+ # This helps to easily identify the index of each solution.
94
+ # Each element has:
95
+ # 1) The index of the solution.
96
+ # 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 ))
99
+ while len (remaining_set ) > 0 :
100
+ # Get the current non-dominated set of solutions.
101
+ d1 , remaining_set = get_non_dominated_set (curr_solutions = remaining_set )
102
+ non_dominated_sets .append (numpy .array (d1 , dtype = object ))
103
+ return non_dominated_sets
104
+
105
+ def crowding_distance (pareto_front ):
106
+ """
107
+ Calculate the crowding dstance for all solutions in the current pareto front.
108
+
109
+ Parameters
110
+ ----------
111
+ pareto_front : TYPE
112
+ The set of solutions in the current pareto front.
113
+
114
+ Returns
115
+ -------
116
+ obj_crowding_dist_list : TYPE
117
+ A nested list of the values for all objectives alongside their crowding distance.
118
+ crowding_dist_sum : TYPE
119
+ A list of the sum of crowding distances across all objectives for each solution.
120
+ """
121
+
122
+ # Each solution in the pareto front has 2 elements:
123
+ # 1) The index of the solution in the population.
124
+ # 2) A list of the fitness values for all objectives of the solution.
125
+ # Before proceeding, remove the indices from each solution in the pareto front.
126
+ pareto_front = numpy .array ([pareto_front [idx ] for idx in range (pareto_front .shape [0 ])])
127
+
128
+ # If there is only 1 solution, then return empty arrays for the crowding distance.
129
+ if pareto_front .shape [0 ] == 1 :
130
+ return numpy .array ([]), numpy .array ([])
63
131
64
- print ("\n \n \n --------------------" )
65
- def crowding_distance (front ):
66
132
# An empty list holding info about the objectives of each solution. The info includes the objective value and crowding distance.
67
133
obj_crowding_dist_list = []
68
134
# Loop through the objectives to calculate the crowding distance of each solution across all objectives.
69
- for obj_idx in range (front .shape [1 ]):
70
- obj = front [:, obj_idx ]
135
+ for obj_idx in range (pareto_front .shape [1 ]):
136
+ obj = pareto_front [:, obj_idx ]
71
137
# This variable has a nested list where each child list zip the following together:
72
138
# 1) The index of the objective value.
73
139
# 2) The objective value.
@@ -79,8 +145,8 @@ def crowding_distance(front):
79
145
obj_sorted = sorted (obj , key = lambda x : x [1 ])
80
146
81
147
# Get the minimum and maximum values for the current objective.
82
- obj_min_val = min (population [:, obj_idx ])
83
- obj_max_val = max (population [:, obj_idx ])
148
+ obj_min_val = min (population_fitness [:, obj_idx ])
149
+ obj_max_val = max (population_fitness [:, obj_idx ])
84
150
denominator = obj_max_val - obj_min_val
85
151
# To avoid division by zero, set the denominator to a tiny value.
86
152
if denominator == 0 :
@@ -93,7 +159,7 @@ def crowding_distance(front):
93
159
# crowding_distance[-1] = inf_val
94
160
obj_sorted [- 1 ][2 ] = inf_val
95
161
96
- # If there are only 2 solutions in the current front, then do not proceed.
162
+ # If there are only 2 solutions in the current pareto front, then do not proceed.
97
163
# The crowding distance for such 2 solutions is infinity.
98
164
if len (obj_sorted ) <= 2 :
99
165
break
@@ -113,11 +179,24 @@ def crowding_distance(front):
113
179
crowding_dist = numpy .array ([obj_crowding_dist_list [idx , :, 2 ] for idx in range (len (obj_crowding_dist_list ))])
114
180
crowding_dist_sum = numpy .sum (crowding_dist , axis = 0 )
115
181
182
+ # An array of the sum of crowding distances across all objectives.
183
+ # Each row has 2 elements:
184
+ # 1) The index of the solution.
185
+ # 2) The sum of all crowding distances for all objective of the solution.
186
+ crowding_dist_sum = numpy .array (list (zip (obj_crowding_dist_list [0 , :, 0 ], crowding_dist_sum )))
187
+ crowding_dist_sum = sorted (crowding_dist_sum , key = lambda x : x [1 ], reverse = True )
188
+
116
189
return obj_crowding_dist_list , crowding_dist_sum
117
190
118
- # Fetch the current front.
119
- front = dominant_set [1 ]
120
- obj_crowding_distance_list , crowding_distance_sum = crowding_distance (front )
191
+ non_dominated_sets = non_dominated_sorting (population_fitness )
192
+
193
+ # for i, s in enumerate(non_dominated_sets):
194
+ # print(f'dominated Pareto Front Set {i+1}:\n{s}')
195
+ # print("\n\n\n--------------------")
196
+
197
+ # Fetch the current pareto front.
198
+ pareto_front = non_dominated_sets [1 ][:, 1 ]
199
+ obj_crowding_distance_list , crowding_distance_sum = crowding_distance (pareto_front )
121
200
122
201
print (obj_crowding_distance_list )
123
- print (f'\n Sum of Crowd Dists\n { crowding_distance_sum } ' )
202
+ print (f'\n Sorted Sum of Crowd Dists\n { crowding_distance_sum } ' )
0 commit comments