Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2825 Correct reference point generation.

File size: 25.4 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Linq;
5using System.Threading;
6using HEAL.Attic;
7using HeuristicLab.Common;
8using HeuristicLab.Core;
9using HeuristicLab.Data;
10using HeuristicLab.Encodings.RealVectorEncoding;
11using HeuristicLab.Optimization;
12using HeuristicLab.Parameters;
13using HeuristicLab.Random;
14
15namespace HeuristicLab.Algorithms.NSGA3
16{
17    /// <summary>
18    /// The Reference Point Based Non-dominated Sorting Genetic Algorithm III was introduced in Deb
19    /// et al. 2013. An Evolutionary Many-Objective Optimization Algorithm Using Reference Point
20    /// Based Non-dominated Sorting Approach. IEEE Transactions on Evolutionary Computation, 18(4),
21    /// pp. 577-601.
22    /// </summary>
23    [Item("NSGA-III", "The Reference Point Based Non-dominated Sorting Genetic Algorithm III was introduced in Deb et al. 2013. An Evolutionary Many-Objective Optimization Algorithm Using Reference Point Based Non-dominated Sorting Approach. IEEE Transactions on Evolutionary Computation, 18(4), pp. 577-601.")]
24    [Creatable(Category = CreatableAttribute.Categories.PopulationBasedAlgorithms, Priority = 136)]
25    [StorableType("07C745F7-A8A3-4F99-8B2C-F97E639F9AC3")]
26    public class NSGA3 : BasicAlgorithm
27    {
28        private const double EPSILON = 10e-6; // a tiny number that is greater than 0
29
30        public override bool SupportsPause => false;
31
32        #region ProblemProperties
33
34        public override Type ProblemType
35        {
36            get { return typeof(MultiObjectiveBasicProblem<RealVectorEncoding>); }
37        }
38
39        public new MultiObjectiveBasicProblem<RealVectorEncoding> Problem
40        {
41            get { return (MultiObjectiveBasicProblem<RealVectorEncoding>)base.Problem; }
42            set { base.Problem = value; }
43        }
44
45        #endregion ProblemProperties
46
47        #region Storable fields
48
49        [Storable]
50        private IRandom random;
51
52        [Storable]
53        private int generation;
54
55        [Storable]
56        private List<Solution> solutions;
57
58        #endregion Storable fields
59
60        #region ParameterAndResultsNames
61
62        // Parameter Names
63
64        private const string SeedName = "Seed";
65        private const string SetSeedRandomlyName = "SetSeedRandomly";
66        private const string PopulationSizeName = "PopulationSize";
67        private const string CrossoverProbabilityName = "CrossoverProbability";
68        private const string CrossoverContiguityName = "CrossoverContiguity";
69        private const string MutationProbabilityName = "MutationProbability";
70        private const string MaximumGenerationsName = "MaximumGenerations";
71        private const string DominateOnEqualQualitiesName = "DominateOnEqualQualities";
72
73        // Results Names
74
75        private const string GeneratedReferencePointsResultName = "Generated Reference Points";
76        private const string CurrentFrontResultName = "Pareto Front"; // Do not touch this
77
78        #endregion ParameterAndResultsNames
79
80        #region ParameterProperties
81
82        private IFixedValueParameter<IntValue> SeedParameter
83        {
84            get { return (IFixedValueParameter<IntValue>)Parameters[SeedName]; }
85        }
86
87        private IFixedValueParameter<BoolValue> SetSeedRandomlyParameter
88        {
89            get { return (IFixedValueParameter<BoolValue>)Parameters[SetSeedRandomlyName]; }
90        }
91
92        private IFixedValueParameter<IntValue> PopulationSizeParameter
93        {
94            get { return (IFixedValueParameter<IntValue>)Parameters[PopulationSizeName]; }
95        }
96
97        private IFixedValueParameter<PercentValue> CrossoverProbabilityParameter
98        {
99            get { return (IFixedValueParameter<PercentValue>)Parameters[CrossoverProbabilityName]; }
100        }
101
102        private IFixedValueParameter<DoubleValue> CrossoverContiguityParameter
103        {
104            get { return (IFixedValueParameter<DoubleValue>)Parameters[CrossoverContiguityName]; }
105        }
106
107        private IFixedValueParameter<PercentValue> MutationProbabilityParameter
108        {
109            get { return (IFixedValueParameter<PercentValue>)Parameters[MutationProbabilityName]; }
110        }
111
112        private IFixedValueParameter<IntValue> MaximumGenerationsParameter
113        {
114            get { return (IFixedValueParameter<IntValue>)Parameters[MaximumGenerationsName]; }
115        }
116
117        private IFixedValueParameter<BoolValue> DominateOnEqualQualitiesParameter
118        {
119            get { return (IFixedValueParameter<BoolValue>)Parameters[DominateOnEqualQualitiesName]; }
120        }
121
122        #endregion ParameterProperties
123
124        #region Properties
125
126        public IntValue Seed => SeedParameter.Value;
127
128        public BoolValue SetSeedRandomly => SetSeedRandomlyParameter.Value;
129
130        public IntValue PopulationSize => PopulationSizeParameter.Value;
131
132        public PercentValue CrossoverProbability => CrossoverProbabilityParameter.Value;
133
134        public DoubleValue CrossoverContiguity => CrossoverContiguityParameter.Value;
135
136        public PercentValue MutationProbability => MutationProbabilityParameter.Value;
137
138        public IntValue MaximumGenerations => MaximumGenerationsParameter.Value;
139
140        public BoolValue DominateOnEqualQualities => DominateOnEqualQualitiesParameter.Value;
141
142        public List<List<Solution>> Fronts { get; private set; }
143
144        public List<ReferencePoint> ReferencePoints { get; private set; }
145
146        // todo: create one property for the Generated Reference Points and one for the current
147        // generations reference points
148
149        #endregion Properties
150
151        #region ResultsProperties
152
153        public DoubleMatrix ResultsGeneratedReferencePoints
154        {
155            get { return (DoubleMatrix)Results[GeneratedReferencePointsResultName].Value; }
156            set { Results[GeneratedReferencePointsResultName].Value = value; }
157        }
158
159        public DoubleMatrix ResultsSolutions
160        {
161            get { return (DoubleMatrix)Results[CurrentFrontResultName].Value; }
162            set { Results[CurrentFrontResultName].Value = value; }
163        }
164
165        #endregion ResultsProperties
166
167        #region Constructors
168
169        public NSGA3() : base()
170        {
171            Parameters.Add(new FixedValueParameter<IntValue>(SeedName, "The random seed used to initialize the new pseudo random number generator.", new IntValue(0)));
172            Parameters.Add(new FixedValueParameter<BoolValue>(SetSeedRandomlyName, "True if the random seed should be set to a random value, otherwise false.", new BoolValue(true)));
173            Parameters.Add(new FixedValueParameter<IntValue>(PopulationSizeName, "The size of the population of Individuals.", new IntValue(200)));
174            Parameters.Add(new FixedValueParameter<PercentValue>(CrossoverProbabilityName, "The probability that the crossover operator is applied on two parents.", new PercentValue(0.9)));
175            Parameters.Add(new FixedValueParameter<DoubleValue>(CrossoverContiguityName, "The contiguity value for the Simulated Binary Crossover that specifies how close a child should be to its parents (larger value means closer). The value must be greater than or equal than 0. Typical values are in the range [2;5]."));
176            Parameters.Add(new FixedValueParameter<PercentValue>(MutationProbabilityName, "The probability that the mutation operator is applied on a Individual.", new PercentValue(0.05)));
177            Parameters.Add(new FixedValueParameter<IntValue>(MaximumGenerationsName, "The maximum number of generations which should be processed.", new IntValue(1000)));
178            Parameters.Add(new FixedValueParameter<BoolValue>(DominateOnEqualQualitiesName, "Flag which determines wether Individuals with equal quality values should be treated as dominated.", new BoolValue(false)));
179        }
180
181        // Persistence uses this ctor to improve deserialization efficiency. If we would use the
182        // default ctor instead this would completely initialize the object (e.g. creating
183        // parameters) even though the data is later overwritten by the stored data.
184        [StorableConstructor]
185        public NSGA3(StorableConstructorFlag _) : base(_) { }
186
187        // Each clonable item must have a cloning ctor (deep cloning, the cloner is used to handle
188        // cyclic object references). Don't forget to call the cloning ctor of the base class
189        public NSGA3(NSGA3 original, Cloner cloner) : base(original, cloner)
190        {
191            // todo: don't forget to clone storable fields
192            random = cloner.Clone(original.random);
193            solutions = new List<Solution>(original.solutions?.Select(cloner.Clone));
194        }
195
196        public override IDeepCloneable Clone(Cloner cloner)
197        {
198            return new NSGA3(this, cloner);
199        }
200
201        #endregion Constructors
202
203        #region Initialization
204
205        protected override void Initialize(CancellationToken cancellationToken)
206        {
207            base.Initialize(cancellationToken);
208
209            InitFields();
210            InitResults();
211            InitReferencePoints();
212            Analyze();
213        }
214
215        private void InitFields()
216        {
217            random = new MersenneTwister();
218            generation = 0;
219            InitSolutions();
220        }
221
222        private void InitSolutions()
223        {
224            int minBound = 0;
225            int maxBound = 1;
226
227            // Initialise solutions
228            solutions = new List<Solution>(PopulationSize.Value);
229            for (int i = 0; i < PopulationSize.Value; i++)
230            {
231                RealVector randomRealVector = new RealVector(Problem.Encoding.Length, random, minBound, maxBound);
232
233                solutions.Add(new Solution(randomRealVector));
234                solutions[i].Fitness = Evaluate(solutions[i].Chromosome);
235            }
236        }
237
238        private void InitReferencePoints()
239        {
240            // Generate reference points and add them to results
241            ReferencePoints = ReferencePoint.GenerateReferencePoints(random, Problem.Maximization.Length);
242            ResultsGeneratedReferencePoints = Utility.ConvertToDoubleMatrix(ReferencePoints);
243        }
244
245        private void InitResults()
246        {
247            Results.Add(new Result(GeneratedReferencePointsResultName, "The initially generated reference points", new DoubleMatrix()));
248            Results.Add(new Result(CurrentFrontResultName, "The Pareto Front", new DoubleMatrix()));
249        }
250
251        #endregion Initialization
252
253        #region Overriden Methods
254
255        protected override void Run(CancellationToken cancellationToken)
256        {
257            while (generation != MaximumGenerations.Value)
258            {
259                // create copies of generated reference points (to preserve the original ones for
260                // the next generation) maybe todo: use cloner?
261                ToNextGeneration(CreateCopyOfReferencePoints());
262                generation++;
263            }
264        }
265
266        #endregion Overriden Methods
267
268        #region Private Methods
269
270        private List<ReferencePoint> CreateCopyOfReferencePoints()
271        {
272            if (ReferencePoints == null) return null;
273
274            List<ReferencePoint> referencePoints = new List<ReferencePoint>();
275            foreach (var referencePoint in ReferencePoints)
276                referencePoints.Add(new ReferencePoint(referencePoint));
277
278            return referencePoints;
279        }
280
281        private void Analyze()
282        {
283            ResultsSolutions = solutions.Select(s => s.Chromosome.ToArray()).ToMatrix();
284            Problem.Analyze(
285                solutions.Select(s => (Individual)new SingleEncodingIndividual(Problem.Encoding, new Scope { Variables = { new Variable(Problem.Encoding.Name, s.Chromosome) } })).ToArray(),
286                solutions.Select(s => s.Fitness).ToArray(),
287                Results,
288                random
289                );
290        }
291
292        /// <summary>
293        /// Returns the fitness of the given <paramref name="chromosome" /> by applying the Evaluate
294        /// method of the Problem.
295        /// </summary>
296        /// <param name="chromosome"></param>
297        /// <returns></returns>
298        private double[] Evaluate(RealVector chromosome)
299        {
300            return Problem.Evaluate(new SingleEncodingIndividual(Problem.Encoding, new Scope { Variables = { new Variable(Problem.Encoding.Name, chromosome) } }), random);
301        }
302
303        private void ToNextGeneration(List<ReferencePoint> referencePoints)
304        {
305            List<Solution> st = new List<Solution>();
306            List<Solution> qt = Mutate(Recombine(solutions));
307            List<Solution> rt = Utility.Concat(solutions, qt);
308            List<Solution> nextGeneration;
309
310            // Do non-dominated sort
311            var qualities = Utility.ToFitnessMatrix(rt);
312            // compute the pareto fronts using the DominationCalculator and discard the qualities
313            // part in the inner tuples
314            Fronts = DominationCalculator<Solution>.CalculateAllParetoFronts(rt.ToArray(), qualities, Problem.Maximization, out int[] rank, true)
315                .Select(list => new List<Solution>(list.Select(pair => pair.Item1))).ToList();
316
317            int i = 0;
318            List<Solution> lf = null; // last front to be included
319            while (i < Fronts.Count && st.Count < PopulationSize.Value)
320            {
321                lf = Fronts[i];
322                st = Utility.Concat(st, lf);
323                i++;
324            }
325
326            if (st.Count == PopulationSize.Value) // no selection needs to be done
327                nextGeneration = st;
328            else
329            {
330                int l = i - 1;
331                nextGeneration = new List<Solution>();
332                for (int f = 0; f < l; f++)
333                    nextGeneration = Utility.Concat(nextGeneration, Fronts[f]);
334                int k = PopulationSize.Value - nextGeneration.Count;
335                Normalize(st);
336                Associate(referencePoints);
337                List<Solution> solutionsToAdd = Niching(k, referencePoints);
338                nextGeneration = Utility.Concat(nextGeneration, solutionsToAdd);
339            }
340        }
341
342        private void Normalize(List<Solution> population)
343        {
344            // Find the ideal point
345            double[] idealPoint = new double[Problem.Encoding.Length];
346            for (int j = 0; j < Problem.Encoding.Length; j++)
347            {
348                // Compute ideal point
349                idealPoint[j] = Utility.Min(s => s.Fitness[j], population);
350
351                // Translate objectives
352                foreach (var solution in population)
353                    solution.Fitness[j] -= idealPoint[j];
354            }
355
356            // Find the extreme points
357            Solution[] extremePoints = new Solution[Problem.Encoding.Length];
358            for (int j = 0; j < Problem.Encoding.Length; j++)
359            {
360                // Compute extreme points
361                double[] weights = new double[Problem.Encoding.Length];
362                for (int i = 0; i < Problem.Encoding.Length; i++) weights[i] = EPSILON;
363                weights[j] = 1;
364                double func(Solution s) => ASF(s.Fitness, weights);
365                extremePoints[j] = Utility.ArgMin(func, population);
366            }
367
368            // Compute intercepts
369            List<double> intercepts = GetIntercepts(extremePoints.ToList());
370
371            // Normalize objectives
372            NormalizeObjectives(intercepts, idealPoint);
373        }
374
375        private void NormalizeObjectives(List<double> intercepts, double[] idealPoint)
376        {
377            for (int f = 0; f < Fronts.Count; f++)
378            {
379                foreach (var solution in Fronts[f])
380                {
381                    for (int i = 0; i < Problem.Encoding.Length; i++)
382                    {
383                        if (Math.Abs(intercepts[i] - idealPoint[i]) > EPSILON)
384                        {
385                            solution.Fitness[i] = solution.Fitness[i] / (intercepts[i] - idealPoint[i]);
386                        }
387                        else
388                        {
389                            solution.Fitness[i] = solution.Fitness[i] / EPSILON;
390                        }
391                    }
392                }
393            }
394        }
395
396        private void Associate(List<ReferencePoint> referencePoints)
397        {
398            for (int f = 0; f < Fronts.Count; f++)
399            {
400                foreach (var solution in Fronts[f])
401                {
402                    // find reference point for which the perpendicular distance to the current
403                    // solution is the lowest
404                    var rpAndDist = Utility.MinArgMin(rp => GetPerpendicularDistance(rp.Values, solution.Fitness), referencePoints);
405                    // associated reference point
406                    var arp = rpAndDist.Item1;
407                    // distance to that reference point
408                    var dist = rpAndDist.Item2;
409
410                    if (f + 1 != Fronts.Count)
411                    {
412                        // Todo: Add member for reference point on index min_rp
413                        arp.NumberOfAssociatedSolutions++;
414                    }
415                    else
416                    {
417                        // Todo: Add potential member for reference point on index min_rp
418                        arp.AddPotentialAssociatedSolution(solution, dist);
419                    }
420                }
421            }
422        }
423
424        private List<Solution> Niching(int k, List<ReferencePoint> referencePoints)
425        {
426            List<Solution> solutions = new List<Solution>();
427            while (solutions.Count != k)
428            {
429                ReferencePoint min_rp = FindNicheReferencePoint(referencePoints);
430
431                Solution chosen = SelectClusterMember(min_rp);
432                if (chosen == null)
433                {
434                    referencePoints.Remove(min_rp);
435                }
436                else
437                {
438                    min_rp.NumberOfAssociatedSolutions++;
439                    min_rp.RemovePotentialAssociatedSolution(chosen);
440                    solutions.Add(chosen);
441                }
442            }
443
444            return solutions;
445        }
446
447        private ReferencePoint FindNicheReferencePoint(List<ReferencePoint> referencePoints)
448        {
449            // the minimum number of associated solutions for a reference point over all reference points
450            int minNumber = Utility.Min(rp => rp.NumberOfAssociatedSolutions, referencePoints);
451
452            // the reference points that share the number of associated solutions where that number
453            // is equal to minNumber
454            List<ReferencePoint> minAssociatedReferencePoints = new List<ReferencePoint>();
455            foreach (var referencePoint in referencePoints)
456                if (referencePoint.NumberOfAssociatedSolutions == minNumber)
457                    minAssociatedReferencePoints.Add(referencePoint);
458
459            if (minAssociatedReferencePoints.Count > 1)
460                return minAssociatedReferencePoints[random.Next(minAssociatedReferencePoints.Count)];
461            else
462                return minAssociatedReferencePoints.Single();
463        }
464
465        private Solution SelectClusterMember(ReferencePoint referencePoint)
466        {
467            Solution chosen = null;
468            if (referencePoint.HasPotentialMember())
469            {
470                if (referencePoint.NumberOfAssociatedSolutions == 0)
471                    chosen = referencePoint.FindClosestMember();
472                else
473                    chosen = referencePoint.RandomMember();
474            }
475            return chosen;
476        }
477
478        private double GetPerpendicularDistance(double[] values, double[] fitness)
479        {
480            double numerator = 0;
481            double denominator = 0;
482            for (int i = 0; i < values.Length; i++)
483            {
484                numerator += values[i] * fitness[i];
485                denominator += Math.Pow(values[i], 2);
486            }
487            double k = numerator / denominator;
488
489            double d = 0;
490            for (int i = 0; i < values.Length; i++)
491            {
492                d += Math.Pow(k * values[i] - fitness[i], 2);
493            }
494            return Math.Sqrt(d);
495        }
496
497        private double ASF(double[] x, double[] weight)
498        {
499            List<int> dimensions = new List<int>();
500            for (int i = 0; i < Problem.Encoding.Length; i++) dimensions.Add(i);
501            double f(int dim) => x[dim] / weight[dim];
502            return Utility.Max(f, dimensions);
503        }
504
505        private List<double> GetIntercepts(List<Solution> extremePoints)
506        {
507            // Check whether there are duplicate extreme points. This might happen but the original
508            // paper does not mention how to deal with it.
509            bool duplicate = false;
510            for (int i = 0; !duplicate && i < extremePoints.Count; i++)
511            {
512                for (int j = i + 1; !duplicate && j < extremePoints.Count; j++)
513                {
514                    // maybe todo: override Equals method of solution?
515                    duplicate = extremePoints[i].Equals(extremePoints[j]);
516                }
517            }
518
519            List<double> intercepts = new List<double>();
520
521            if (duplicate)
522            { // cannot construct the unique hyperplane (this is a casual method to deal with the condition)
523                for (int f = 0; f < Problem.Encoding.Length; f++)
524                {
525                    // extreme_points[f] stands for the individual with the largest value of
526                    // objective f
527                    intercepts.Add(extremePoints[f].Fitness[f]);
528                }
529            }
530            else
531            {
532                // Find the equation of the hyperplane
533                List<double> b = new List<double>(); //(pop[0].objs().size(), 1.0);
534                for (int i = 0; i < Problem.Encoding.Length; i++)
535                {
536                    b.Add(1.0);
537                }
538
539                List<List<double>> a = new List<List<double>>();
540                foreach (Solution s in extremePoints)
541                {
542                    List<double> aux = new List<double>();
543                    for (int i = 0; i < Problem.Encoding.Length; i++)
544                        aux.Add(s.Fitness[i]);
545                    a.Add(aux);
546                }
547                List<double> x = GaussianElimination(a, b);
548
549                // Find intercepts
550                for (int f = 0; f < Problem.Encoding.Length; f++)
551                {
552                    intercepts.Add(1.0 / x[f]);
553                }
554            }
555
556            return intercepts;
557        }
558
559        private List<double> GaussianElimination(List<List<double>> a, List<double> b)
560        {
561            List<double> x = new List<double>();
562
563            int n = a.Count;
564            for (int i = 0; i < n; i++)
565                a[i].Add(b[i]);
566
567            for (int @base = 0; @base < n - 1; @base++)
568                for (int target = @base + 1; target < n; target++)
569                {
570                    double ratio = a[target][@base] / a[@base][@base];
571                    for (int term = 0; term < a[@base].Count; term++)
572                        a[target][term] = a[target][term] - a[@base][term] * ratio;
573                }
574
575            for (int i = 0; i < n; i++)
576                x.Add(0.0);
577
578            for (int i = n - 1; i >= 0; i--)
579            {
580                for (int known = i + 1; known < n; known++)
581                    a[i][n] = a[i][n] - a[i][known] * x[known];
582                x[i] = a[i][n] / a[i][i];
583            }
584
585            return x;
586        }
587
588        private List<Solution> Recombine(List<Solution> solutions)
589        {
590            List<Solution> childSolutions = new List<Solution>();
591
592            for (int i = 0; i < solutions.Count; i += 2)
593            {
594                int parentIndex1 = random.Next(solutions.Count);
595                int parentIndex2 = random.Next(solutions.Count);
596                // ensure that the parents are not the same object
597                if (parentIndex1 == parentIndex2) parentIndex2 = (parentIndex2 + 1) % solutions.Count;
598                var parent1 = solutions[parentIndex1];
599                var parent2 = solutions[parentIndex2];
600
601                // Do crossover with crossoverProbabilty == 1 in order to guarantee that a crossover happens
602                var children = SimulatedBinaryCrossover.Apply(random, Problem.Encoding.Bounds, parent1.Chromosome, parent2.Chromosome, 1);
603                Debug.Assert(children != null);
604
605                var child1 = new Solution(children.Item1);
606                var child2 = new Solution(children.Item2);
607                child1.Fitness = Evaluate(child1.Chromosome);
608                child2.Fitness = Evaluate(child1.Chromosome);
609
610                childSolutions.Add(child1);
611                childSolutions.Add(child2);
612            }
613
614            return childSolutions;
615        }
616
617        private List<Solution> Mutate(List<Solution> solutions)
618        {
619            foreach (var solution in solutions)
620            {
621                UniformOnePositionManipulator.Apply(random, solution.Chromosome, Problem.Encoding.Bounds);
622            }
623            return solutions;
624        }
625
626        #endregion Private Methods
627    }
628}
Note: See TracBrowser for help on using the repository browser.