Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2825 Add Generational Distance and Inverted Generational Distance to result page.

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