Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2825-NSGA3/HeuristicLab.Algorithms.NSGA3/3.3/NSGA3Selection.cs

Last change on this file was 17727, checked in by dleko, 4 years ago

#2825 Refactoring.

File size: 14.6 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using HEAL.Attic;
5using HeuristicLab.Common;
6using HeuristicLab.Core;
7using HeuristicLab.Optimization;
8
9namespace HeuristicLab.Algorithms.NSGA3
10{
11    [StorableType("53158EA5-4D1C-42DB-90F5-EC555EC0A450")]
12    public class NSGA3Selection : IDeepCloneable
13    {
14        private const double EPSILON = 10e-6; // a tiny number that is greater than 0
15
16        [Storable]
17        private int numberOfObjectives;
18
19        [Storable]
20        private bool[] maximization;
21
22        [Storable]
23        private int populationSize;
24
25        [Storable]
26        private IRandom random;
27
28        [Storable]
29        private bool dominateOnEqualQualities;
30
31        [StorableConstructor]
32        public NSGA3Selection(StorableConstructorFlag _) { }
33
34        public NSGA3Selection(int numberOfObjectives, bool[] maximization, int populationSize, IRandom random, bool dominateOnEqualQualities)
35        {
36            if (maximization == null) throw new ArgumentNullException(nameof(maximization));
37            if (random == null) throw new ArgumentNullException(nameof(random));
38            if (numberOfObjectives != maximization.Length) throw new ArgumentException($"{nameof(numberOfObjectives)} and the length of {nameof(maximization)} do not match.");
39            if (populationSize < 1) throw new ArgumentOutOfRangeException($"{nameof(populationSize)} has to be >= 1.");
40
41            this.numberOfObjectives = numberOfObjectives;
42            this.maximization = maximization;
43            this.populationSize = populationSize;
44            this.random = random;
45            this.dominateOnEqualQualities = dominateOnEqualQualities;
46        }
47
48        public NSGA3Selection(NSGA3Selection other, Cloner cloner)
49        {
50            numberOfObjectives = other.numberOfObjectives;
51            maximization = new bool[other.maximization.Length];
52            Array.Copy(other.maximization, maximization, maximization.Length);
53            populationSize = other.populationSize;
54            random = cloner.Clone(other.random);
55            dominateOnEqualQualities = other.dominateOnEqualQualities;
56        }
57
58        public IDeepCloneable Clone(Cloner cloner)
59        {
60            return new NSGA3Selection(this, cloner);
61        }
62
63        public object Clone()
64        {
65            return new Cloner().Clone(this);
66        }
67
68        /// <summary>
69        /// Select the solutions for the next generation from <see cref="rt" /> according to the
70        /// NSGA-III algorithm.
71        /// </summary>
72        /// <param name="rt">The current generation and its mutated children.</param>
73        /// <param name="referencePoints"></param>
74        /// <returns></returns>
75        public List<Solution> SelectSolutionsForNextGeneration(List<Solution> rt, List<ReferencePoint> referencePoints)
76        {
77            List<Solution> st = new List<Solution>();
78
79            // Do non-dominated sort
80            var qualities = Utility.ToFitnessMatrix(rt);
81
82            // compute the pareto fronts using the DominationCalculator
83            List<List<Solution>> fronts = DominationCalculator<Solution>.CalculateAllParetoFronts(rt.ToArray(), qualities, maximization, out int[] rank, dominateOnEqualQualities)
84                .Select(list => new List<Solution>(list.Select(pair => pair.Item1))).ToList();
85
86            int last = 0; // last front to be included
87            while (st.Count < populationSize)
88            {
89                st = Utility.Concat(st, fronts[last++]);
90            }
91
92            // remove useless fronts
93            fronts.RemoveRange(last, fronts.Count - last);
94
95            List<Solution> nextGeneration = new List<Solution>();
96            if (st.Count == populationSize)
97            { // no special selection needs to be done
98                nextGeneration = st;
99            }
100            else
101            {
102                // Add every front to the next generation list except for the one that is added only partially
103                nextGeneration = new List<Solution>();
104                for (int f = 0; f < last - 1; f++)
105                    nextGeneration = Utility.Concat(nextGeneration, fronts[f]);
106
107                int k = populationSize - nextGeneration.Count;
108                Normalize(rt, fronts);
109                Associate(fronts, referencePoints);
110                List<Solution> solutionsToAdd = Niching(k, referencePoints, random);
111                nextGeneration = Utility.Concat(nextGeneration, solutionsToAdd);
112            }
113
114            return nextGeneration;
115        }
116
117        private void Normalize(List<Solution> solutions, List<List<Solution>> fronts)
118        {
119            // Find the ideal point
120            double[] idealPoint = TranslateObjectives(solutions, fronts[0]);
121
122            // Find the extreme points
123            List<Solution> extremePoints = GetExtremePoints(fronts[0]);
124
125            // Compute intercepts
126            double[] intercepts = GetIntercepts(extremePoints, fronts[0]);
127
128            // Normalize objectives
129            NormalizeObjectives(solutions, intercepts, idealPoint);
130        }
131
132        private double[] TranslateObjectives(List<Solution> solutions, List<Solution> firstFront)
133        {
134            double[] idealPoint = new double[numberOfObjectives];
135            foreach (var solution in solutions)
136            {
137                if (solution.NormalizedObjectives == null) solution.NormalizedObjectives = new double[numberOfObjectives];
138            }
139
140            for (int j = 0; j < numberOfObjectives; j++)
141            {
142                // Compute ideal point
143                idealPoint[j] = Utility.Min(s => s.Objectives[j], firstFront);
144
145                // Translate objectives and save the modified fitness values in NormalizedObjectives
146                foreach (var solution in solutions)
147                    solution.NormalizedObjectives[j] = solution.Objectives[j] - idealPoint[j];
148            }
149
150            return idealPoint;
151        }
152
153        private List<Solution> GetExtremePoints(List<Solution> firstFront)
154        {
155            List<Solution> extremePoints = new List<Solution>(numberOfObjectives);
156            for (int j = 0; j < numberOfObjectives; j++)
157            {
158                // Compute extreme points
159                double[] weights = new double[numberOfObjectives];
160                for (int i = 0; i < numberOfObjectives; i++) weights[i] = EPSILON;
161                weights[j] = 1;
162
163                var extremePoint = Utility.ArgMin(s => ASF(s.NormalizedObjectives, weights), firstFront);
164                extremePoints.Add(extremePoint);
165            }
166
167            return extremePoints;
168        }
169
170        private double ASF(double[] convertedFitness, double[] weight)
171        {
172            var dims = Enumerable.Range(0, numberOfObjectives);
173
174            return Utility.Max((dim) => convertedFitness[dim] / weight[dim], dims);
175        }
176
177        private double[] GetIntercepts(List<Solution> extremePoints, List<Solution> solutions)
178        {
179            // Check whether there are duplicate extreme points. This might happen but the original
180            // paper does not mention how to deal with it.
181            bool duplicate = false;
182            for (int i = 0; !duplicate && i < extremePoints.Count; i++)
183            {
184                for (int j = i + 1; !duplicate && j < extremePoints.Count; j++)
185                {
186                    duplicate = extremePoints[i] == extremePoints[j];
187                }
188            }
189
190            double[] intercepts = new double[numberOfObjectives];
191
192            bool negative_intercept = false;
193            if (!duplicate)
194            {
195                // Find the equation of the hyperplane
196                List<double> b = new List<double>(Enumerable.Repeat(1.0, numberOfObjectives));
197
198                List<List<double>> a = new List<List<double>>(extremePoints.Count);
199
200                for (int i = 0; i < extremePoints.Count; i++)
201                {
202                    List<double> convertedFitness = new List<double>(extremePoints[i].NormalizedObjectives);
203                    a.Add(convertedFitness);
204                }
205                List<double> x = GaussianElimination(a, b);
206
207                // Find intercepts
208                for (int i = 0; i < intercepts.Length; i++)
209                {
210                    intercepts[i] = 1.0 / x[i];
211
212                    if (x[i] < 0)
213                    {
214                        negative_intercept = true;
215                        break;
216                    }
217                }
218            }
219
220            // follow the method in Yuan et al. (GECCO 2015)
221            if (duplicate || negative_intercept)
222            {
223                double[] maxObjectives = FindMaxObjectives(solutions);
224                for (int i = 0; i < intercepts.Length; i++)
225                    intercepts[i] = maxObjectives[i];
226            }
227
228            return intercepts;
229        }
230
231        private List<double> GaussianElimination(List<List<double>> a, List<double> b)
232        {
233            List<double> x = new List<double>();
234
235            int n = a.Count;
236            for (int i = 0; i < n; i++)
237                a[i].Add(b[i]);
238
239            for (int @base = 0; @base < n - 1; @base++)
240                for (int target = @base + 1; target < n; target++)
241                {
242                    double ratio = a[target][@base] / a[@base][@base];
243                    for (int term = 0; term < a[@base].Count; term++)
244                        a[target][term] = a[target][term] - a[@base][term] * ratio;
245                }
246
247            for (int i = 0; i < n; i++)
248                x.Add(0.0);
249
250            for (int i = n - 1; i >= 0; i--)
251            {
252                for (int known = i + 1; known < n; known++)
253                    a[i][n] = a[i][n] - a[i][known] * x[known];
254                x[i] = a[i][n] / a[i][i];
255            }
256
257            return x;
258        }
259
260        private double[] FindMaxObjectives(List<Solution> solutions)
261        {
262            double[] maxPoint = new double[numberOfObjectives];
263            for (int i = 0; i < maxPoint.Length; i++) maxPoint[i] = double.MinValue;
264
265            for (int f = 0; f < numberOfObjectives; f++)
266            {
267                maxPoint[f] = Utility.Max(s => s.Objectives[f], solutions);
268            }
269
270            return maxPoint;
271        }
272
273        private void NormalizeObjectives(List<Solution> solutions, double[] intercepts, double[] idealPoint)
274        {
275            foreach (var solution in solutions)
276            {
277                for (int i = 0; i < numberOfObjectives; i++)
278                {
279                    if (Math.Abs(intercepts[i] - idealPoint[i]) > 10e-10)
280                    {
281                        solution.NormalizedObjectives[i] = solution.NormalizedObjectives[i] / (intercepts[i] - idealPoint[i]);
282                    }
283                    else
284                    {
285                        solution.NormalizedObjectives[i] = solution.NormalizedObjectives[i] / 10e-10;
286                    }
287                }
288            }
289        }
290
291        private void Associate(List<List<Solution>> fronts, List<ReferencePoint> referencePoints)
292        {
293            for (int f = 0; f < fronts.Count; f++)
294            {
295                foreach (var solution in fronts[f])
296                {
297                    // find reference point for which the perpendicular distance to the current
298                    // solution is the lowest
299                    var rpAndDist = Utility.MinArgMin(rp => GetPerpendicularDistance(rp.Values, solution.NormalizedObjectives), referencePoints);
300                    // associated reference point
301                    var arp = rpAndDist.Item1;
302                    // distance to that reference point
303                    var dist = rpAndDist.Item2;
304
305                    if (f + 1 != fronts.Count)
306                    {
307                        arp.NumberOfAssociatedSolutions++;
308                    }
309                    else
310                    {
311                        arp.AddPotentialAssociatedSolution(solution, dist);
312                    }
313                }
314            }
315        }
316
317        private double GetPerpendicularDistance(double[] direction, double[] point)
318        {
319            double numerator = 0;
320            double denominator = 0;
321            for (int i = 0; i < direction.Length; i++)
322            {
323                numerator += direction[i] * point[i];
324                denominator += Math.Pow(direction[i], 2);
325            }
326            double k = numerator / denominator;
327
328            double d = 0;
329            for (int i = 0; i < direction.Length; i++)
330            {
331                d += Math.Pow(k * direction[i] - point[i], 2);
332            }
333            return Math.Sqrt(d);
334        }
335
336        private List<Solution> Niching(int k, List<ReferencePoint> referencePoints, IRandom random)
337        {
338            List<Solution> solutions = new List<Solution>();
339            while (solutions.Count != k)
340            {
341                ReferencePoint min_rp = FindNicheReferencePoint(referencePoints, random);
342
343                Solution chosen = SelectClusterMember(min_rp);
344                if (chosen == null)
345                {
346                    referencePoints.Remove(min_rp);
347                }
348                else
349                {
350                    min_rp.NumberOfAssociatedSolutions++;
351                    min_rp.RemovePotentialAssociatedSolution(chosen);
352                    solutions.Add(chosen);
353                }
354            }
355
356            return solutions;
357        }
358
359        private ReferencePoint FindNicheReferencePoint(List<ReferencePoint> referencePoints, IRandom random)
360        {
361            // the minimum number of associated solutions for a reference point over all reference points
362            int minNumber = Utility.Min(rp => rp.NumberOfAssociatedSolutions, referencePoints);
363
364            var minReferencePoints = new List<ReferencePoint>(referencePoints.Where(r => r.NumberOfAssociatedSolutions == minNumber));
365            if (minReferencePoints.Count == 1) return minReferencePoints.Single();
366            else return minReferencePoints[random.Next(minReferencePoints.Count)];
367        }
368
369        private Solution SelectClusterMember(ReferencePoint referencePoint)
370        {
371            Solution chosen = null;
372            if (referencePoint.HasPotentialMember())
373            {
374                if (referencePoint.NumberOfAssociatedSolutions == 0)
375                    chosen = referencePoint.FindClosestMember();
376                else
377                    chosen = referencePoint.RandomMember();
378            }
379            return chosen;
380        }
381    }
382}
Note: See TracBrowser for help on using the repository browser.