Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.StructureIdentification/Manipulation/ChangeNodeTypeManipulation.cs @ 162

Last change on this file since 162 was 162, checked in by gkronber, 16 years ago

added description for GP manipulation operator ChangeNodeTypeManipulation

File size: 11.1 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2008 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26using HeuristicLab.Core;
27using HeuristicLab.Operators;
28using HeuristicLab.Random;
29using HeuristicLab.Data;
30using HeuristicLab.Constraints;
31using HeuristicLab.Functions;
32
33namespace HeuristicLab.StructureIdentification {
34  public class ChangeNodeTypeManipulation : OperatorBase {
35
36    public override string Description {
37      get {
38        return @"This manipulation operator selects a random tree-node and changes the function type.
39If this leads to a constraint-violation (wrong number or type of sub-trees) the sub-trees are repaired
40resulting in a valid tree again.";
41      }
42    }
43
44    public ChangeNodeTypeManipulation()
45      : base() {
46      AddVariableInfo(new VariableInfo("Random", "Uniform random number generator", typeof(MersenneTwister), VariableKind.In));
47      AddVariableInfo(new VariableInfo("OperatorLibrary", "The operator library containing all available operators", typeof(GPOperatorLibrary), VariableKind.In));
48      AddVariableInfo(new VariableInfo("MaxTreeHeight", "The maximal allowed height of the tree", typeof(IntData), VariableKind.In));
49      AddVariableInfo(new VariableInfo("MaxTreeSize", "The maximal allowed size (number of nodes) of the tree", typeof(IntData), VariableKind.In));
50      AddVariableInfo(new VariableInfo("BalancedTreesRate", "Determines how many trees should be balanced", typeof(DoubleData), VariableKind.In));
51      AddVariableInfo(new VariableInfo("FunctionTree", "The tree to mutate", typeof(IFunctionTree), VariableKind.In | VariableKind.Out));
52      AddVariableInfo(new VariableInfo("TreeSize", "The size (number of nodes) of the tree", typeof(IntData), VariableKind.In | VariableKind.Out));
53      AddVariableInfo(new VariableInfo("TreeHeight", "The height of the tree", typeof(IntData), VariableKind.In | VariableKind.Out));
54    }
55
56
57    public override IOperation Apply(IScope scope) {
58      IFunctionTree root = GetVariableValue<IFunctionTree>("FunctionTree", scope, false);
59      MersenneTwister random = GetVariableValue<MersenneTwister>("Random", scope, true);
60      GPOperatorLibrary library = GetVariableValue<GPOperatorLibrary>("OperatorLibrary", scope, true);
61      double balancedTreesRate = GetVariableValue<DoubleData>("BalancedTreesRate", scope, true).Data;
62      IntData treeSize = GetVariableValue<IntData>("TreeSize", scope, false);
63      IntData treeHeight = GetVariableValue<IntData>("TreeHeight", scope, false);
64      int maxTreeSize = GetVariableValue<IntData>("MaxTreeSize", scope, true).Data;
65      int maxTreeHeight = GetVariableValue<IntData>("MaxTreeHeight", scope, true).Data;
66
67      TreeGardener gardener = new TreeGardener(random, library);
68      IFunctionTree parent = gardener.GetRandomParentNode(root);
69
70      IFunctionTree selectedChild;
71      int selectedChildIndex;
72      if (parent == null) {
73        selectedChildIndex = 0;
74        selectedChild = root;
75      } else {
76        selectedChildIndex = random.Next(parent.SubTrees.Count);
77        selectedChild = parent.SubTrees[selectedChildIndex];
78      }
79
80      if (selectedChild.SubTrees.Count == 0) {
81        IFunctionTree newTerminal = ChangeTerminalType(parent, selectedChild, selectedChildIndex, gardener, random);
82
83        if (parent == null) {
84          // no parent means the new child is the initial operator
85          // and we have to update the value in the variable
86          scope.GetVariable(scope.TranslateName("FunctionTree")).Value = newTerminal;
87        } else {
88          parent.RemoveSubTree(selectedChildIndex);
89          parent.InsertSubTree(selectedChildIndex, newTerminal);
90          // updating the variable is not necessary because it stays the same
91        }
92        if(!gardener.IsValidTree(root)) throw new InvalidProgramException();
93
94        // size and height stays the same when changing a terminal so no need to update the variables
95        // schedule an operation to initialize the new terminal
96        return gardener.CreateInitializationOperation(gardener.GetAllSubTrees(newTerminal), scope);
97      } else {
98        List<IFunctionTree> uninitializedBranches;
99        IFunctionTree newFunction = ChangeFunctionType(parent, selectedChild, selectedChildIndex, gardener, random, balancedTreesRate, out uninitializedBranches);
100
101        if (parent == null) {
102          // no parent means the new function is the initial operator
103          // and we have to update the value in the variable
104          scope.GetVariable(scope.TranslateName("FunctionTree")).Value = newFunction;
105          root = newFunction;
106        } else {
107          // remove the old child
108          parent.RemoveSubTree(selectedChildIndex);
109          // add the new child as sub-tree of parent
110          parent.InsertSubTree(selectedChildIndex, newFunction);
111        }
112
113        // recalculate size and height
114        treeSize.Data = gardener.GetTreeSize(root);
115        treeHeight.Data = gardener.GetTreeHeight(root);
116
117        // check if whole tree is ok
118        // check if the size of the new tree is still in the allowed bounds
119        if (!gardener.IsValidTree(root) ||
120          treeHeight.Data > maxTreeHeight ||
121          treeSize.Data > maxTreeSize) {
122          throw new InvalidProgramException();
123        }
124
125        // return a composite operation that initializes all created sub-trees
126        return gardener.CreateInitializationOperation(uninitializedBranches, scope);
127      }
128    }
129
130    private IFunctionTree ChangeTerminalType(IFunctionTree parent, IFunctionTree child, int childIndex, TreeGardener gardener, MersenneTwister random) {
131      IList<IFunction> allowedChildren;
132      if (parent == null) {
133        allowedChildren = gardener.Terminals;
134      } else {
135        allowedChildren = new List<IFunction>();
136        var allAllowedChildren = gardener.GetAllowedSubFunctions(parent.Function, childIndex);
137        foreach(IFunction c in allAllowedChildren) {
138          if(gardener.IsTerminal(c)) allowedChildren.Add(c);
139        }
140      }
141
142      // selecting from the terminals should always work since the current child was also a terminal
143      // so in the worst case we will just create a new terminal of the same type again.
144      return gardener.CreateRandomTree(allowedChildren[random.Next(allowedChildren.Count)], 1, 1, false);
145    }
146
147    private IFunctionTree ChangeFunctionType(IFunctionTree parent, IFunctionTree child, int childIndex, TreeGardener gardener, MersenneTwister random,
148      double balancedTreesRate, out List<IFunctionTree> uninitializedBranches) {
149      // since there are subtrees, we have to check which
150      // and how many of the existing subtrees we can reuse
151
152      // let's choose the function we want to use instead of the old child. For this we have to determine the
153      // pool of allowed functions based on constraints of the parent if there is one.
154      IList<IFunction> allowedFunctions = gardener.GetAllowedSubFunctions(parent!=null?parent.Function:null, childIndex);
155
156      // try to make a tree with the same arity as the old child.
157      int actualArity = child.SubTrees.Count;
158      // arity of the selected operator
159      int minArity;
160      int maxArity;
161      // only allow functions where we can keep all existing sub-trees
162      // we don't want to create new sub-trees here
163      // this restriction can be removed if we add code that creates sub-trees where necessary (gkronber 22.04.08)
164      allowedFunctions = allowedFunctions.Where(f => {
165        gardener.GetMinMaxArity(f, out minArity, out maxArity);
166        return minArity <= actualArity;
167      }).ToList();
168
169      // create a new tree-node for a randomly selected function
170      IFunctionTree newTree = new FunctionTree(allowedFunctions[random.Next(allowedFunctions.Count)]);
171
172      gardener.GetMinMaxArity(newTree.Function, out minArity, out maxArity);
173      // if the old child had too many sub-trees then the new child should keep as many sub-trees as possible
174      if (actualArity > maxArity)
175        actualArity = maxArity;
176
177      // get the allowed size and height for new sub-trees
178      // use the size of the smallest subtree as the maximal allowed size for new subtrees to
179      // prevent that we ever create trees over the MaxTreeSize limit
180      int maxSubTreeSize = child.SubTrees.Select(subTree => gardener.GetTreeSize(subTree)).Min();
181      int maxSubTreeHeight = gardener.GetTreeHeight(child) - 1;
182
183      // create a list that holds old sub-trees that we can reuse in the new tree
184      List<IFunctionTree> availableSubTrees = new List<IFunctionTree>(child.SubTrees);
185      List<IFunctionTree> freshSubTrees = new List<IFunctionTree>() { newTree };
186
187      // randomly select the sub-trees that we keep
188      for (int i = 0; i < actualArity; i++) {
189        // fill all sub-tree slots of the new tree
190        // if for a given slot i there are multiple existing sub-trees that can be used in that slot
191        // then use a random existing sub-tree. When there are no existing sub-trees
192        // that fit in the given slot then create a new random tree and use it for the slot
193        ICollection<IFunction> allowedSubFunctions = gardener.GetAllowedSubFunctions(newTree.Function, i);
194        var matchingSubTrees = availableSubTrees.Where(subTree => allowedSubFunctions.Contains(subTree.Function));
195
196        if (matchingSubTrees.Count() > 0) {
197          IFunctionTree selectedSubTree = matchingSubTrees.ElementAt(random.Next(matchingSubTrees.Count()));
198          // we can just add it as subtree
199          newTree.InsertSubTree(i, selectedSubTree);
200          availableSubTrees.Remove(selectedSubTree); // the branch shouldn't be available for the following slots
201        } else {
202          // no existing matching tree found => create a new one
203          IFunctionTree freshTree;
204          if(random.NextDouble() <= balancedTreesRate) {
205            freshTree = gardener.CreateRandomTree(allowedSubFunctions, maxSubTreeSize, maxSubTreeHeight, true);
206          } else {
207            freshTree = gardener.CreateRandomTree(allowedSubFunctions, maxSubTreeSize, maxSubTreeHeight, false);
208          }
209          freshSubTrees.AddRange(gardener.GetAllSubTrees(freshTree));
210
211          newTree.InsertSubTree(i, freshTree);
212        }
213      }
214      uninitializedBranches = freshSubTrees;
215      return newTree;
216    }
217  }
218}
Note: See TracBrowser for help on using the repository browser.