Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2825 Bugfix: NSGA3 now works as intended.

File size: 20.5 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Threading;
5using HEAL.Attic;
6using HeuristicLab.Common;
7using HeuristicLab.Core;
8using HeuristicLab.Data;
9using HeuristicLab.Encodings.RealVectorEncoding;
10using HeuristicLab.Optimization;
11using HeuristicLab.Parameters;
12using HeuristicLab.Problems.TestFunctions.MultiObjective;
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        public override bool SupportsPause => false;
29
30        #region ProblemProperties
31
32        public override Type ProblemType
33        {
34            get { return typeof(MultiObjectiveBasicProblem<RealVectorEncoding>); }
35        }
36
37        public new MultiObjectiveBasicProblem<RealVectorEncoding> Problem
38        {
39            get { return (MultiObjectiveBasicProblem<RealVectorEncoding>)base.Problem; }
40            set { base.Problem = value; }
41        }
42
43        public int NumberOfObjectives
44        {
45            get
46            {
47                if (!(Problem is MultiObjectiveTestFunctionProblem testFunctionProblem)) throw new NotSupportedException("Only Multi Objective Test Function problems are supported");
48                return testFunctionProblem.Objectives;
49            }
50        }
51
52        #endregion ProblemProperties
53
54        #region Storable fields
55
56        [Storable]
57        private IRandom random;
58
59        [Storable]
60        private List<Solution> solutions; // maybe todo: rename to nextGeneration (see Run method)
61
62        [Storable]
63        private List<ReferencePoint> referencePoints;
64
65        #endregion Storable fields
66
67        #region ParameterAndResultsNames
68
69        // Parameter Names
70
71        private const string SeedName = "Seed";
72        private const string SetSeedRandomlyName = "Set Seed Randomly";
73        private const string PopulationSizeName = "Population Size";
74        private const string CrossoverProbabilityName = "Crossover Probability";
75        private const string CrossoverContiguityName = "Crossover Contiguity";
76        private const string MutationProbabilityName = "Mutation Probability";
77        private const string MaximumGenerationsName = "Maximum Generations";
78        private const string DominateOnEqualQualitiesName = "Dominate On Equal Qualities";
79
80        // Results Names
81
82        private const string GeneratedReferencePointsResultName = "Generated Reference Points";
83        private const string CurrentGenerationResultName = "Generations";
84        private const string GenerationalDistanceResultName = "Generational Distance";
85        private const string InvertedGenerationalDistanceResultName = "Inverted Generational Distance";
86        private const string ScatterPlotResultName = "Scatter Plot";
87        private const string CurrentFrontResultName = "Pareto Front"; // Do not touch this
88
89        #endregion ParameterAndResultsNames
90
91        #region ParameterProperties
92
93        private IFixedValueParameter<IntValue> SeedParameter
94        {
95            get { return (IFixedValueParameter<IntValue>)Parameters[SeedName]; }
96        }
97
98        private IFixedValueParameter<BoolValue> SetSeedRandomlyParameter
99        {
100            get { return (IFixedValueParameter<BoolValue>)Parameters[SetSeedRandomlyName]; }
101        }
102
103        private IFixedValueParameter<IntValue> PopulationSizeParameter
104        {
105            get { return (IFixedValueParameter<IntValue>)Parameters[PopulationSizeName]; }
106        }
107
108        private IFixedValueParameter<PercentValue> CrossoverProbabilityParameter
109        {
110            get { return (IFixedValueParameter<PercentValue>)Parameters[CrossoverProbabilityName]; }
111        }
112
113        private IFixedValueParameter<DoubleValue> CrossoverContiguityParameter
114        {
115            get { return (IFixedValueParameter<DoubleValue>)Parameters[CrossoverContiguityName]; }
116        }
117
118        private IFixedValueParameter<PercentValue> MutationProbabilityParameter
119        {
120            get { return (IFixedValueParameter<PercentValue>)Parameters[MutationProbabilityName]; }
121        }
122
123        private IFixedValueParameter<IntValue> MaximumGenerationsParameter
124        {
125            get { return (IFixedValueParameter<IntValue>)Parameters[MaximumGenerationsName]; }
126        }
127
128        private IFixedValueParameter<BoolValue> DominateOnEqualQualitiesParameter
129        {
130            get { return (IFixedValueParameter<BoolValue>)Parameters[DominateOnEqualQualitiesName]; }
131        }
132
133        #endregion ParameterProperties
134
135        #region Properties
136
137        public IntValue Seed => SeedParameter.Value;
138
139        public BoolValue SetSeedRandomly => SetSeedRandomlyParameter.Value;
140
141        public IntValue PopulationSize => PopulationSizeParameter.Value;
142
143        public PercentValue CrossoverProbability => CrossoverProbabilityParameter.Value;
144
145        public DoubleValue CrossoverContiguity => CrossoverContiguityParameter.Value;
146
147        public PercentValue MutationProbability => MutationProbabilityParameter.Value;
148
149        public IntValue MaximumGenerations => MaximumGenerationsParameter.Value;
150
151        public BoolValue DominateOnEqualQualities => DominateOnEqualQualitiesParameter.Value;
152
153        // todo: create one property for the Generated Reference Points and one for the current
154        // generations reference points
155
156        #endregion Properties
157
158        #region ResultsProperties
159
160        public DoubleMatrix ResultsGeneratedReferencePoints
161        {
162            get { return (DoubleMatrix)Results[GeneratedReferencePointsResultName].Value; }
163            set { Results[GeneratedReferencePointsResultName].Value = value; }
164        }
165
166        public DoubleMatrix ResultsSolutions
167        {
168            get { return (DoubleMatrix)Results[CurrentFrontResultName].Value; }
169            set { Results[CurrentFrontResultName].Value = value; }
170        }
171
172        public IntValue ResultsCurrentGeneration
173        {
174            get { return (IntValue)Results[CurrentGenerationResultName].Value; }
175            set { Results[CurrentGenerationResultName].Value = value; }
176        }
177
178        public DoubleValue ResultsGenerationalDistance
179        {
180            get { return (DoubleValue)Results[GenerationalDistanceResultName].Value; }
181            set { Results[GenerationalDistanceResultName].Value = value; }
182        }
183
184        public DoubleValue ResultsInvertedGenerationalDistance
185        {
186            get { return (DoubleValue)Results[InvertedGenerationalDistanceResultName].Value; }
187            set { Results[InvertedGenerationalDistanceResultName].Value = value; }
188        }
189
190        public ParetoFrontScatterPlot ResultsScatterPlot
191        {
192            get { return (ParetoFrontScatterPlot)Results[ScatterPlotResultName].Value; }
193            set { Results[ScatterPlotResultName].Value = value; }
194        }
195
196        #endregion ResultsProperties
197
198        #region Constructors
199
200        public NSGA3() : base()
201        {
202            Parameters.Add(new FixedValueParameter<IntValue>(SeedName, "The random seed used to initialize the new pseudo random number generator.", new IntValue(0)));
203            Parameters.Add(new FixedValueParameter<BoolValue>(SetSeedRandomlyName, "True if the random seed should be set to a random value, otherwise false.", new BoolValue(true)));
204            Parameters.Add(new FixedValueParameter<IntValue>(PopulationSizeName, "The size of the population of Individuals.", new IntValue(200)));
205            Parameters.Add(new FixedValueParameter<PercentValue>(CrossoverProbabilityName, "The probability that the crossover operator is applied on two parents.", new PercentValue(1.0)));
206            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]."));
207            Parameters.Add(new FixedValueParameter<PercentValue>(MutationProbabilityName, "The probability that the mutation operator is applied on a Individual.", new PercentValue(0.05)));
208            Parameters.Add(new FixedValueParameter<IntValue>(MaximumGenerationsName, "The maximum number of generations which should be processed.", new IntValue(1000)));
209            Parameters.Add(new FixedValueParameter<BoolValue>(DominateOnEqualQualitiesName, "Flag which determines wether Individuals with equal quality values should be treated as dominated.", new BoolValue(false)));
210        }
211
212        // Persistence uses this ctor to improve deserialization efficiency. If we would use the
213        // default ctor instead this would completely initialize the object (e.g. creating
214        // parameters) even though the data is later overwritten by the stored data.
215        [StorableConstructor]
216        public NSGA3(StorableConstructorFlag _) : base(_) { }
217
218        // Each clonable item must have a cloning ctor (deep cloning, the cloner is used to handle
219        // cyclic object references). Don't forget to call the cloning ctor of the base class
220        public NSGA3(NSGA3 original, Cloner cloner) : base(original, cloner)
221        {
222            // todo: don't forget to clone storable fields
223            random = cloner.Clone(original.random);
224            solutions = original.solutions != null ? original.solutions.Select(cloner.Clone).ToList() : null;
225            referencePoints = original.referencePoints != null ? original.referencePoints.Select(r =>
226            {
227                var refPoint = new ReferencePoint(random, r.NumberOfDimensions);
228                r.Values.CopyTo(refPoint.Values, 0);
229                return refPoint;
230            }).ToList() : null;
231        }
232
233        public override IDeepCloneable Clone(Cloner cloner)
234        {
235            return new NSGA3(this, cloner);
236        }
237
238        #endregion Constructors
239
240        #region Initialization
241
242        protected override void Initialize(CancellationToken cancellationToken)
243        {
244            base.Initialize(cancellationToken);
245
246            // Set population size
247            int numberOfGeneratedReferencePoints = ReferencePoint.GetNumberOfGeneratedReferencePoints(NumberOfObjectives);
248            int pop = ((numberOfGeneratedReferencePoints + 3) / 4) * 4;
249            PopulationSize.Value = pop;
250
251            // Set mutation probability
252            double mutationProbability = 1.0 / Problem.Encoding.Length;
253            MutationProbability.Value = mutationProbability;
254
255            InitResults();
256            InitFields();
257            Analyze();
258        }
259
260        private void InitResults()
261        {
262            Results.Add(new Result(GeneratedReferencePointsResultName, "The initially generated reference points", new DoubleMatrix()));
263            Results.Add(new Result(CurrentFrontResultName, "The Pareto Front", new DoubleMatrix()));
264            Results.Add(new Result(CurrentGenerationResultName, "The current generation", new IntValue(1)));
265            Results.Add(new Result(GenerationalDistanceResultName, "The generational distance to an optimal pareto front defined in the Problem", new DoubleValue(double.NaN)));
266            Results.Add(new Result(InvertedGenerationalDistanceResultName, "The inverted generational distance to an optimal pareto front defined in the Problem", new DoubleValue(double.NaN)));
267            Results.Add(new Result(ScatterPlotResultName, "A scatterplot displaying the evaluated solutions and (if available) the analytically optimal front", new ParetoFrontScatterPlot()));
268
269            var problem = Problem as MultiObjectiveTestFunctionProblem;
270            if (problem == null) return;
271            // todo: add BestKnownFront parameter
272            ResultsScatterPlot = new ParetoFrontScatterPlot(new double[0][], new double[0][], problem.BestKnownFront.ToJaggedArray(), problem.Objectives, problem.ProblemSize);
273        }
274
275        private void InitFields()
276        {
277            random = new MersenneTwister();
278            InitSolutions();
279            InitReferencePoints();
280        }
281
282        private void InitReferencePoints()
283        {
284            // Generate reference points and add them to results
285            referencePoints = ReferencePoint.GenerateReferencePoints(random, NumberOfObjectives);
286            ResultsGeneratedReferencePoints = Utility.ConvertToDoubleMatrix(referencePoints);
287        }
288
289        private void InitSolutions()
290        {
291            int minBound = 0;
292            int maxBound = 1;
293
294            // Initialise solutions
295            solutions = new List<Solution>(PopulationSize.Value);
296            for (int i = 0; i < PopulationSize.Value; i++)
297            {
298                RealVector randomRealVector = new RealVector(Problem.Encoding.Length, random, minBound, maxBound);
299                var solution = new Solution(randomRealVector);
300                solution.Fitness = Evaluate(solution.Chromosome);
301                solutions.Add(solution);
302            }
303        }
304
305        #endregion Initialization
306
307        #region Overriden Methods
308
309        protected override void Run(CancellationToken cancellationToken)
310        {
311            while (ResultsCurrentGeneration.Value < MaximumGenerations.Value)
312            {
313                try
314                {
315                    // todo: make parameter out of this
316                    List<Solution> qt = Mutate(Recombine(solutions), 30);
317                    foreach (var solution in qt)
318                        solution.Fitness = Evaluate(solution.Chromosome);
319
320                    List<Solution> rt = Utility.Concat(solutions, qt);
321
322                    solutions = NSGA3Selection.SelectSolutionsForNextGeneration(rt, GetCopyOfReferencePoints(), Problem.Maximization, PopulationSize.Value, random);
323
324                    ResultsCurrentGeneration.Value++;
325                    Analyze();
326                    cancellationToken.ThrowIfCancellationRequested();
327                }
328                catch (OperationCanceledException ex)
329                {
330                    throw new OperationCanceledException("Optimization process was cancelled.", ex);
331                }
332                catch (Exception ex)
333                {
334                    throw new Exception($"Failed in generation {ResultsCurrentGeneration}.", ex);
335                }
336                finally
337                {
338                    Analyze();
339                }
340            }
341        }
342
343        #endregion Overriden Methods
344
345        #region Private Methods
346
347        private List<ReferencePoint> GetCopyOfReferencePoints()
348        {
349            if (referencePoints == null) return null;
350
351            List<ReferencePoint> referencePointsCopy = new List<ReferencePoint>();
352            foreach (var referencePoint in referencePoints)
353                referencePointsCopy.Add(new ReferencePoint(referencePoint));
354
355            return referencePointsCopy;
356        }
357
358        private void Analyze()
359        {
360            ResultsScatterPlot = new ParetoFrontScatterPlot(solutions.Select(x => x.Fitness).ToArray(), solutions.Select(x => x.Chromosome.ToArray()).ToArray(), ResultsScatterPlot.ParetoFront, ResultsScatterPlot.Objectives, ResultsScatterPlot.ProblemSize);
361            ResultsSolutions = solutions.Select(s => s.Chromosome.ToArray()).ToMatrix();
362
363            var problem = Problem as MultiObjectiveTestFunctionProblem;
364            if (problem == null) return;
365
366            ResultsGenerationalDistance = new DoubleValue(problem.BestKnownFront != null ? GenerationalDistance.Calculate(solutions.Select(s => s.Fitness), problem.BestKnownFront.ToJaggedArray(), 1) : double.NaN);
367            ResultsInvertedGenerationalDistance = new DoubleValue(problem.BestKnownFront != null ? InvertedGenerationalDistance.Calculate(solutions.Select(s => s.Fitness), problem.BestKnownFront.ToJaggedArray(), 1) : double.NaN);
368
369            Problem.Analyze(
370                solutions.Select(s => (Individual)new SingleEncodingIndividual(Problem.Encoding, new Scope { Variables = { new Variable(Problem.Encoding.Name, s.Chromosome) } })).ToArray(),
371                solutions.Select(s => s.Fitness).ToArray(),
372                Results,
373                random
374                );
375        }
376
377        /// <summary>
378        /// Returns the fitness of the given <paramref name="chromosome" /> by applying the Evaluate
379        /// method of the Problem.
380        /// </summary>
381        /// <param name="chromosome"></param>
382        /// <returns></returns>
383        private double[] Evaluate(RealVector chromosome)
384        {
385            return Problem.Evaluate(new SingleEncodingIndividual(Problem.Encoding, new Scope { Variables = { new Variable(Problem.Encoding.Name, chromosome) } }), random);
386        }
387
388        private List<Solution> Recombine(List<Solution> solutions)
389        {
390            List<Solution> childSolutions = new List<Solution>();
391
392            for (int i = 0; i < solutions.Count; i += 2)
393            {
394                int parentIndex1 = random.Next(solutions.Count);
395                int parentIndex2 = random.Next(solutions.Count);
396                // ensure that the parents are not the same object
397                if (parentIndex1 == parentIndex2) parentIndex2 = (parentIndex2 + 1) % solutions.Count;
398                var parent1 = solutions[parentIndex1];
399                var parent2 = solutions[parentIndex2];
400
401                // Do crossover with crossoverProbabilty == 1 in order to guarantee that a crossover happens
402                var children = SimulatedBinaryCrossover.Apply(random,
403                Problem.Encoding.Bounds, parent1.Chromosome, parent2.Chromosome, CrossoverProbability.Value);
404
405                childSolutions.Add(new Solution(children.Item1));
406                childSolutions.Add(new Solution(children.Item2));
407            }
408
409            return childSolutions;
410        }
411
412        private List<Solution> Mutate(List<Solution> solutions, double eta)
413        {
414            foreach (var solution in solutions)
415            {
416                for (int i = 0; i < solution.Chromosome.Length; i++)
417                {
418                    if (random.NextDouble() > MutationProbability.Value) continue;
419
420                    double y = solution.Chromosome[i];
421                    double lb;
422                    double ub;
423                    var problem = Problem as MultiObjectiveTestFunctionProblem;
424                    if (problem.Bounds.Rows == 1) lb = problem.Bounds[0, 0];
425                    else lb = problem.Bounds[i, 0];
426                    if (problem.Bounds.Rows == 1) ub = problem.Bounds[0, 1];
427                    else ub = problem.Bounds[i, 1];
428
429                    double delta1 = (y - lb) / (ub - lb);
430                    double delta2 = (ub - y) / (ub - lb);
431
432                    double mut_pow = 1.0 / (eta + 1.0);
433
434                    double rnd = random.NextDouble();
435                    var deltaq = 0.0;
436                    if (rnd <= 0.5)
437                    {
438                        double xy = 1.0 - delta1;
439                        double val = 2.0 * rnd + (1.0 - 2.0 * rnd) * (Math.Pow(xy, (eta + 1.0)));
440                        deltaq = Math.Pow(val, mut_pow) - 1.0;
441                    }
442                    else
443                    {
444                        double xy = 1.0 - delta2;
445                        double val = 2.0 * (1.0 - rnd) + 2.0 * (rnd - 0.5) * (Math.Pow(xy, (eta + 1.0)));
446                        deltaq = 1.0 - (Math.Pow(val, mut_pow));
447                    }
448
449                    y = y + deltaq * (ub - lb);
450                    y = Math.Min(ub, Math.Max(lb, y));
451
452                    solution.Chromosome[i] = y;
453                }
454            }
455            return solutions;
456        }
457
458        #endregion Private Methods
459    }
460}
Note: See TracBrowser for help on using the repository browser.