SDDSlib
Loading...
Searching...
No Matches
non_dominated_sort.c
Go to the documentation of this file.
1/**
2 * @file non_dominated_sort.c
3 * @brief Definitions and implementations for non-dominated sorting and crowding distance routines used in multi-objective optimization.
4 *
5 * This file provides the core functions for performing non-dominated sorting on a population of individuals,
6 * calculating crowding distances, and sorting the population based on dominance and crowding. It includes
7 * utility functions for managing linked lists used in the sorting process.
8 *
9 * @copyright
10 * - (c) 2002 The University of Chicago, as Operator of Argonne National Laboratory.
11 * - (c) 2002 The Regents of the University of California, as Operator of Los Alamos National Laboratory.
12 *
13 * @license
14 * This file is distributed under the terms of the Software License Agreement
15 * found in the file LICENSE included with this distribution.
16 *
17 * @author H. Shang, R. Soliday
18 */
19
20#include "mdb.h"
21#include "non_dominated_sort.h"
22
23/* Insert an element X into the list at location specified by NODE */
24void insert_node(list *node, long x) {
25 list *temp;
26 if (node == NULL) {
27 printf("\n Error!! asked to enter after a NULL pointer, hence exiting \n");
28 exit(1);
29 }
30 temp = (list *)malloc(sizeof(list));
31 temp->index = x;
32 temp->child = node->child;
33 temp->parent = node;
34 if (node->child != NULL)
35 node->child->parent = temp;
36 node->child = temp;
37 return;
38}
39
40/* Delete the node NODE from the list */
41list *del_node(list *node) {
42 list *temp;
43 if (node == NULL) {
44 printf("\n Error!! asked to delete a NULL pointer, hence exiting \n");
45 exit(1);
46 }
47 temp = node->parent;
48 temp->child = node->child;
49 if (temp->child != NULL)
50 temp->child->parent = temp;
51 free(node);
52 return (temp);
53}
54
55/* Routine for usual non-domination checking
56 It will return the following values
57 1 if a dominates b
58 -1 if b dominates a
59 0 if both a and b are non-dominated */
60long check_dominance(individual *a, individual *b, long nobj) {
61 long i;
62 long flag1;
63 long flag2;
64 flag1 = 0;
65 flag2 = 0;
66 if (a->constr_violation < 0 && b->constr_violation < 0) {
67 if (a->constr_violation > b->constr_violation)
68 return (1);
69 else if (a->constr_violation < b->constr_violation)
70 return (-1);
71 else
72 return (0);
73 } else {
74 if (a->constr_violation < 0 && b->constr_violation >= 0)
75 return (-1);
76 else if (a->constr_violation >= 0 && b->constr_violation < 0)
77 return (1);
78 else {
79 for (i = 0; i < nobj; i++) {
80 if (a->obj[i] < b->obj[i])
81 flag1 = 1;
82 else if (a->obj[i] > b->obj[i])
83 flag2 = 1;
84 }
85 if (flag1 == 1 && flag2 == 0)
86 return (1);
87 else if (flag1 == 0 && flag2 == 1)
88 return (-1);
89 else
90 return (0);
91 }
92 }
93}
94
95/* Routine to compute crowding distance based on ojbective function values when the population in in the form of a list */
96void assign_crowding_distance_list(population *pop, list *lst, long front_size, long start_i, int64_t *sorted_index) {
97 long **obj_array;
98 long *dist;
99 long i, j, nobj;
100 list *temp;
101
102 temp = lst;
103 nobj = pop->nobj;
104 if (front_size == 1) {
105 pop->ind[lst->index].crowd_dist = INF;
106 return;
107 } else if (front_size == 2) {
108 pop->ind[lst->index].crowd_dist = INF;
109 pop->ind[lst->child->index].crowd_dist = INF;
110 return;
111 }
112 obj_array = (long **)malloc(nobj * sizeof(long));
113 dist = (long *)malloc(front_size * sizeof(long));
114 for (i = 0; i < nobj; i++)
115 obj_array[i] = (long *)malloc(front_size * sizeof(long));
116
117 for (j = 0; j < front_size; j++) {
118 dist[j] = temp->index;
119 temp = temp->child;
120 }
121 assign_crowding_distance(pop, dist, obj_array, front_size, nobj);
122 q_sort_dist(pop, dist, 0, front_size - 1);
123 for (j = 0; j < front_size; j++)
124 sorted_index[start_i + j] = dist[j];
125 free(dist);
126 for (i = 0; i < nobj; i++)
127 free(obj_array[i]);
128 free(obj_array);
129 return;
130}
131
132/* Routine to compute crowding distance based on objective function values when the population in in the form of an array */
133void assign_crowding_distance_indices(population *pop, long c1, long c2, long nobj) {
134 long **obj_array;
135 long *dist;
136 long i, j;
137 long front_size;
138 front_size = c2 - c1 + 1;
139 if (front_size == 1) {
140 pop->ind[c1].crowd_dist = INF;
141 return;
142 } else if (front_size == 2) {
143 pop->ind[c1].crowd_dist = INF;
144 pop->ind[c2].crowd_dist = INF;
145 return;
146 }
147 obj_array = (long **)malloc(nobj * sizeof(long));
148 dist = (long *)malloc(front_size * sizeof(long));
149 for (i = 0; i < nobj; i++)
150 obj_array[i] = (long *)malloc(front_size * sizeof(long));
151 for (j = 0; j < front_size; j++)
152 dist[j] = c1++;
153 assign_crowding_distance(pop, dist, obj_array, front_size, nobj);
154 free(dist);
155 for (i = 0; i < nobj; i++)
156 free(obj_array[i]);
157 free(obj_array);
158 return;
159}
160
161/* Randomized quick sort routine to sort a population based on a
162 particular objective chosen */
163void quicksort_front_obj(population *pop, long objcount, long obj_array[], long obj_array_size) {
164 q_sort_front_obj(pop, objcount, obj_array, 0, obj_array_size - 1);
165 return;
166}
167
168/* Actual implementation of the randomized quick sort used to sort
169 a population based on a particular objective chosen */
170void q_sort_front_obj(population *pop, long objcount, long obj_array[], long left, long right) {
171 long index;
172 long temp;
173 long i, j;
174 double pivot;
175 if (left < right) {
176 index = (left + right) / 2;
177 temp = obj_array[right];
178 obj_array[right] = obj_array[index];
179 obj_array[index] = temp;
180 pivot = pop->ind[obj_array[right]].obj[objcount];
181 i = left - 1;
182 for (j = left; j < right; j++) {
183 if (pop->ind[obj_array[j]].obj[objcount] <= pivot) {
184 i += 1;
185 temp = obj_array[j];
186 obj_array[j] = obj_array[i];
187 obj_array[i] = temp;
188 }
189 }
190 index = i + 1;
191 temp = obj_array[index];
192 obj_array[index] = obj_array[right];
193 obj_array[right] = temp;
194 q_sort_front_obj(pop, objcount, obj_array, left, index - 1);
195 q_sort_front_obj(pop, objcount, obj_array, index + 1, right);
196 }
197 return;
198}
199
200/* Randomized quick sort routine to sort a population based on crowding distance */
201void quicksort_dist(population *pop, long *dist, long front_size) {
202 q_sort_dist(pop, dist, 0, front_size - 1);
203 return;
204}
205
206/* Actual implementation of the randomized quick sort used to sort a population based on crowding distance */
207void q_sort_dist(population *pop, long *dist, long left, long right) {
208 long index;
209 long temp;
210 long i, j;
211 double pivot;
212 if (left < right) {
213 index = (left + right) / 2;
214 temp = dist[right];
215 dist[right] = dist[index];
216 dist[index] = temp;
217 pivot = pop->ind[dist[right]].crowd_dist;
218 i = left - 1;
219 for (j = left; j < right; j++) {
220 if (pop->ind[dist[j]].crowd_dist > pivot) {
221 i += 1;
222 temp = dist[j];
223 dist[j] = dist[i];
224 dist[i] = temp;
225 }
226 }
227 index = i + 1;
228 temp = dist[index];
229 dist[index] = dist[right];
230 dist[right] = temp;
231 q_sort_dist(pop, dist, left, index - 1);
232 q_sort_dist(pop, dist, index + 1, right);
233 }
234 return;
235}
236
237/* Routine to compute crowding distances */
238void assign_crowding_distance(population *pop, long *dist, long **obj_array, long front_size, long nobj) {
239 long i, j;
240 for (i = 0; i < nobj; i++) {
241 for (j = 0; j < front_size; j++)
242 obj_array[i][j] = dist[j];
243 quicksort_front_obj(pop, i, obj_array[i], front_size);
244 }
245 for (j = 0; j < front_size; j++)
246 pop->ind[dist[j]].crowd_dist = 0.0;
247 for (i = 0; i < nobj; i++)
248 pop->ind[obj_array[i][0]].crowd_dist = INF;
249 for (i = 0; i < nobj; i++) {
250 for (j = 1; j < front_size - 1; j++) {
251 if (pop->ind[obj_array[i][j]].crowd_dist != INF) {
252 if (pop->ind[obj_array[i][front_size - 1]].obj[i] == pop->ind[obj_array[i][0]].obj[i])
253 pop->ind[obj_array[i][j]].crowd_dist += 0.0;
254 else
255 pop->ind[obj_array[i][j]].crowd_dist += (pop->ind[obj_array[i][j + 1]].obj[i] - pop->ind[obj_array[i][j - 1]].obj[i]) / (pop->ind[obj_array[i][front_size - 1]].obj[i] - pop->ind[obj_array[i][0]].obj[i]);
256 }
257 }
258 }
259 for (j = 0; j < front_size; j++) {
260 if (pop->ind[dist[j]].crowd_dist != INF)
261 pop->ind[dist[j]].crowd_dist = (pop->ind[dist[j]].crowd_dist) / nobj;
262 }
263 return;
264}
265
266/**
267 * @brief Performs non-dominated sorting on a population and assigns ranks and crowding distances.
268 *
269 * This function sorts the population into different fronts based on non-domination, assigns a rank to each individual
270 * indicating its front, and computes the crowding distance to maintain diversity within each front.
271 *
272 * @param new_pop Pointer to the population structure to be sorted.
273 * @return Array of sorted indices representing the population ordered by rank and crowding distance.
274 */
275int64_t *non_dominated_sort(population *new_pop) {
276 long flag, popsize, i, end, front_size, rank = 1, j;
277 int64_t *sorted_index = NULL;
278 list *orig;
279 list *cur;
280 list *temp1, *temp2;
281 orig = (list *)malloc(sizeof(list));
282 cur = (list *)malloc(sizeof(list));
283 front_size = 0;
284 orig->index = -1;
285 orig->parent = NULL;
286 orig->child = NULL;
287 cur->index = -1;
288 cur->parent = NULL;
289 cur->child = NULL;
290 temp1 = orig;
291 popsize = new_pop->popsize;
292 sorted_index = (int64_t *)malloc(sizeof(*sorted_index) * popsize);
293 for (i = 0; i < popsize; i++) {
294 sorted_index[i] = i;
295 insert_node(temp1, i);
296 temp1 = temp1->child;
297 }
298 i = j = 0;
299 do {
300 if (orig->child->child == NULL) {
301 new_pop->ind[orig->child->index].rank = rank;
302 new_pop->ind[orig->child->index].crowd_dist = INF;
303 sorted_index[i++] = orig->child->index;
304 free(orig->child);
305 break;
306 }
307 temp1 = orig->child;
308 insert_node(cur, temp1->index);
309 front_size = 1;
310 temp2 = cur->child;
311 temp1 = del_node(temp1);
312 temp1 = temp1->child;
313 do {
314 temp2 = cur->child;
315 do {
316 end = 0;
317 flag = check_dominance(&(new_pop->ind[temp1->index]),
318 &(new_pop->ind[temp2->index]), new_pop->nobj);
319 if (flag == 1) {
320 insert_node(orig, temp2->index);
321 temp2 = del_node(temp2);
322 front_size--;
323 temp2 = temp2->child;
324 }
325 if (flag == 0)
326 temp2 = temp2->child;
327 else if (flag == -1)
328 end = 1;
329 } while (end != 1 && temp2 != NULL);
330 if (flag == 0 || flag == 1) {
331 insert_node(cur, temp1->index);
332 front_size++;
333 temp1 = del_node(temp1);
334 }
335 temp1 = temp1->child;
336 } while (temp1 != NULL);
337 temp2 = cur->child;
338 do {
339 new_pop->ind[temp2->index].rank = rank;
340 sorted_index[i++] = temp2->index;
341 temp2 = temp2->child;
342 } while (temp2 != NULL);
343 assign_crowding_distance_list(new_pop, cur->child, front_size, j, sorted_index);
344 temp2 = cur->child;
345 do {
346 j++;
347 temp2 = del_node(temp2);
348 temp2 = temp2->child;
349 } while (cur->child != NULL);
350 rank += 1;
351 } while (orig->child != NULL);
352 free(orig);
353 free(cur);
354 return sorted_index;
355}
356
357/**
358 * @brief Initializes and fills the population structure with individuals and their objective values.
359 *
360 * This function allocates memory for the population's individuals, sets up their objective values based on the
361 * provided column values, and initializes constraint violations if provided.
362 *
363 * @param pop Pointer to the population structure to be filled.
364 * @param rows The number of individuals in the population.
365 * @param columns The number of objective functions.
366 * @param columnValue 2D array containing the objective values for each individual and objective.
367 * @param maximize Array indicating which objectives should be maximized (non-zero value) or minimized.
368 * @param const_violation Array containing constraint violation values for each individual, or NULL if no constraints.
369 */
370void fill_population(population *pop, long rows, long columns, double **columnValue, long *maximize, double *const_violation) {
371 long i, j;
372 if (!pop) {
373 fprintf(stderr, "Null pointer passed to fill_population.\n");
374 exit(1);
375 }
376 pop->ind = (individual *)malloc(rows * sizeof(individual));
377 pop->popsize = rows;
378 pop->nbin = pop->ncons = pop->nreal = 0;
379 pop->nbits = NULL;
380 pop->nobj = columns;
381 for (i = 0; i < rows; i++) {
382 pop->ind[i].xreal = NULL;
383 pop->ind[i].xbin = NULL;
384 pop->ind[i].gene = NULL;
385 pop->ind[i].obj = (double *)malloc(sizeof(double) * pop->nobj);
386 if (const_violation)
387 pop->ind[i].constr_violation = const_violation[i];
388 else
389 pop->ind[i].constr_violation = 0;
390 pop->ind[i].constr = NULL;
391 pop->ind[i].rank = 0;
392 pop->ind[i].crowd_dist = 0;
393 for (j = 0; j < columns; j++) {
394 if (maximize[j])
395 pop->ind[i].obj[j] = -1.0 * columnValue[j][i];
396 else
397 pop->ind[i].obj[j] = columnValue[j][i];
398 }
399 }
400 return;
401}
402
403/**
404 * @brief Frees all dynamically allocated memory associated with a population.
405 *
406 * This function deallocates memory for all individuals in the population, including their objective values, binary variables,
407 * constraints, genes, and other dynamically allocated fields.
408 *
409 * @param pop Pointer to the population structure whose memory is to be freed.
410 */
411void free_pop_mem(population *pop) {
412 long i, j;
413 for (i = 0; i < pop->popsize; i++) {
414 if (pop->ind[i].xreal)
415 free(pop->ind[i].xreal);
416 if (pop->ind[i].obj)
417 free(pop->ind[i].obj);
418 if (pop->ind[i].xbin)
419 free(pop->ind[i].xbin);
420 if (pop->ind[i].constr)
421 free(pop->ind[i].constr);
422 for (j = 0; j < pop->nbin; j++)
423 if (pop->ind[i].gene && pop->ind[i].gene[j])
424 free(pop->ind[i].gene[j]);
425 if (pop->ind[i].gene)
426 free(pop->ind[i].gene);
427 }
428 if (pop->ind)
429 free(pop->ind);
430 if (pop->nbits)
431 free(pop->nbits);
432}
int64_t * non_dominated_sort(population *new_pop)
Performs non-dominated sorting on a population and assigns ranks and crowding distances.
void fill_population(population *pop, long rows, long columns, double **columnValue, long *maximize, double *const_violation)
Initializes and fills the population structure with individuals and their objective values.
void free_pop_mem(population *pop)
Frees all dynamically allocated memory associated with a population.