Skip to content

Commit d8461fa

Browse files
committed
Update crowding distance
1 parent cf4dc63 commit d8461fa

File tree

1 file changed

+133
-54
lines changed

1 file changed

+133
-54
lines changed
Lines changed: 133 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,139 @@
11
import numpy
22

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):
2137
if idx1 == idx2:
2238
continue
2339
# Zipping the 2 solutions so the corresponding genes are in the same list.
2440
# 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+
2743
#TODO Consider repacing < by > for maximization problems.
2844
# 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]
3349

3450
# 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.
3652
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)
4257
break
4358
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.
4660
pass
4761

4862
# 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.
5177
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.
5482
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 = []
6086

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([])
63131

64-
print("\n\n\n--------------------")
65-
def crowding_distance(front):
66132
# An empty list holding info about the objectives of each solution. The info includes the objective value and crowding distance.
67133
obj_crowding_dist_list = []
68134
# 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]
71137
# This variable has a nested list where each child list zip the following together:
72138
# 1) The index of the objective value.
73139
# 2) The objective value.
@@ -79,8 +145,8 @@ def crowding_distance(front):
79145
obj_sorted = sorted(obj, key=lambda x: x[1])
80146

81147
# 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])
84150
denominator = obj_max_val - obj_min_val
85151
# To avoid division by zero, set the denominator to a tiny value.
86152
if denominator == 0:
@@ -93,7 +159,7 @@ def crowding_distance(front):
93159
# crowding_distance[-1] = inf_val
94160
obj_sorted[-1][2] = inf_val
95161

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.
97163
# The crowding distance for such 2 solutions is infinity.
98164
if len(obj_sorted) <= 2:
99165
break
@@ -113,11 +179,24 @@ def crowding_distance(front):
113179
crowding_dist = numpy.array([obj_crowding_dist_list[idx, :, 2] for idx in range(len(obj_crowding_dist_list))])
114180
crowding_dist_sum = numpy.sum(crowding_dist, axis=0)
115181

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+
116189
return obj_crowding_dist_list, crowding_dist_sum
117190

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)
121200

122201
print(obj_crowding_distance_list)
123-
print(f'\nSum of Crowd Dists\n{crowding_distance_sum}')
202+
print(f'\nSorted Sum of Crowd Dists\n{crowding_distance_sum}')

0 commit comments

Comments
 (0)