Skip to content

Commit 069f530

Browse files
committed
NSGA-II Crowding Distance
1 parent 473f1a3 commit 069f530

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import numpy
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):
21+
if idx1 == idx2:
22+
continue
23+
# Zipping the 2 solutions so the corresponding genes are in the same list.
24+
# The returned array is of size (N, 2) where N is the number of genes.
25+
b = numpy.array(list(zip(sol1, sol2)))
26+
27+
#TODO Consider repacing < by > for maximization problems.
28+
# 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]
33+
34+
# 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.
36+
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)
42+
break
43+
else:
44+
# Reaching here means the solution does not dominant the current solution.
45+
# print(f"{sol2} does not dominate {sol1}")
46+
pass
47+
48+
# If the flag is True, then no solution dominates the current solution.
49+
if is_dominant:
50+
dominant_set.append(sol1)
51+
52+
# Return the dominant and non-dominant sets.
53+
return dominant_set, non_dominant_set
54+
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))
60+
61+
for i, s in enumerate(dominant_set):
62+
print(f'Dominant Front Set {i+1}:\n{s}')
63+
64+
print("\n\n\n--------------------")
65+
def crowding_distance(front):
66+
# An empty list holding info about the objectives of each solution. The info includes the objective value and crowding distance.
67+
obj_crowding_dist_list = []
68+
# 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]
71+
# This variable has a nested list where each child list zip the following together:
72+
# 1) The index of the objective value.
73+
# 2) The objective value.
74+
# 3) Initialize the crowding distance by zero.
75+
obj = list(zip(range(len(obj)), obj, [0]*len(obj)))
76+
obj = [list(element) for element in obj]
77+
# This variable is the sorted version where sorting is done by the objective value (second element).
78+
# Note that the first element is still the original objective index before sorting.
79+
obj_sorted = sorted(obj, key=lambda x: x[1])
80+
81+
# Get the minimum and maximum values for the current objective.
82+
obj_min_val = obj_sorted[0][1]
83+
obj_max_val = obj_sorted[-1][1]
84+
denominator = obj_max_val - obj_min_val
85+
# To avoid division by zero, set the denominator to a tiny value.
86+
if denominator == 0:
87+
denominator = 0.0000001
88+
89+
# Set the crowding distance to the first and last solutions (after being sorted) to infinity.
90+
inf_val = 999999999
91+
# crowding_distance[0] = inf_val
92+
obj_sorted[0][2] = inf_val
93+
# crowding_distance[-1] = inf_val
94+
obj_sorted[-1][2] = inf_val
95+
96+
# If there are only 2 solutions in the current front, then do not proceed.
97+
# The crowding distance for such 2 solutions is infinity.
98+
if len(obj_sorted) <= 2:
99+
break
100+
101+
for idx in range(1, len(obj_sorted)-1):
102+
# Calculate the crowding distance.
103+
crowding_dist = obj_sorted[idx+1][1] - obj_sorted[idx-1][1]
104+
crowding_dist = crowding_dist / denominator
105+
# Insert the crowding distance back into the list to override the initial zero.
106+
obj_sorted[idx][2] = crowding_dist
107+
108+
# Sort the objective by the original index at index 0 of the each child list.
109+
obj_sorted = sorted(obj_sorted, key=lambda x: x[0])
110+
obj_crowding_dist_list.append(obj_sorted)
111+
112+
obj_crowding_dist_list = numpy.array(obj_crowding_dist_list)
113+
crowding_dist = numpy.array([obj_crowding_dist_list[idx, :, 2] for idx in range(len(obj_crowding_dist_list))])
114+
crowding_dist_sum = numpy.sum(crowding_dist, axis=0)
115+
116+
return obj_crowding_dist_list, crowding_dist_sum
117+
118+
# Fetch the current front.
119+
front = dominant_set[1]
120+
obj_crowding_distance_list, crowding_distance_sum = crowding_distance(front)
121+
122+
print(obj_crowding_distance_list)
123+
print(f'\nSum of Crowd Dists\n{crowding_distance_sum}')

0 commit comments

Comments
 (0)