Free cookie consent management tool by TermsFeed Policy Generator

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

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

merged FunctionsAndStructIdRefactoring-branch (r142, r143, r144, r145, r146, r147, r148, r149, r152, r153) back into the trunk (ticket #112)

File size: 10.8 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    public ChangeNodeTypeManipulation()
36      : base() {
37      AddVariableInfo(new VariableInfo("Random", "Uniform random number generator", typeof(MersenneTwister), VariableKind.In));
38      AddVariableInfo(new VariableInfo("OperatorLibrary", "The operator library containing all available operators", typeof(GPOperatorLibrary), VariableKind.In));
39      AddVariableInfo(new VariableInfo("MaxTreeHeight", "The maximal allowed height of the tree", typeof(IntData), VariableKind.In));
40      AddVariableInfo(new VariableInfo("MaxTreeSize", "The maximal allowed size (number of nodes) of the tree", typeof(IntData), VariableKind.In));
41      AddVariableInfo(new VariableInfo("BalancedTreesRate", "Determines how many trees should be balanced", typeof(DoubleData), VariableKind.In));
42      AddVariableInfo(new VariableInfo("FunctionTree", "The tree to mutate", typeof(IFunctionTree), VariableKind.In | VariableKind.Out));
43      AddVariableInfo(new VariableInfo("TreeSize", "The size (number of nodes) of the tree", typeof(IntData), VariableKind.In | VariableKind.Out));
44      AddVariableInfo(new VariableInfo("TreeHeight", "The height of the tree", typeof(IntData), VariableKind.In | VariableKind.Out));
45    }
46
47
48    public override IOperation Apply(IScope scope) {
49      IFunctionTree root = GetVariableValue<IFunctionTree>("FunctionTree", scope, false);
50      MersenneTwister random = GetVariableValue<MersenneTwister>("Random", scope, true);
51      GPOperatorLibrary library = GetVariableValue<GPOperatorLibrary>("OperatorLibrary", scope, true);
52      double balancedTreesRate = GetVariableValue<DoubleData>("BalancedTreesRate", scope, true).Data;
53      IntData treeSize = GetVariableValue<IntData>("TreeSize", scope, false);
54      IntData treeHeight = GetVariableValue<IntData>("TreeHeight", scope, false);
55      int maxTreeSize = GetVariableValue<IntData>("MaxTreeSize", scope, true).Data;
56      int maxTreeHeight = GetVariableValue<IntData>("MaxTreeHeight", scope, true).Data;
57
58      TreeGardener gardener = new TreeGardener(random, library);
59      IFunctionTree parent = gardener.GetRandomParentNode(root);
60
61      IFunctionTree selectedChild;
62      int selectedChildIndex;
63      if (parent == null) {
64        selectedChildIndex = 0;
65        selectedChild = root;
66      } else {
67        selectedChildIndex = random.Next(parent.SubTrees.Count);
68        selectedChild = parent.SubTrees[selectedChildIndex];
69      }
70
71      if (selectedChild.SubTrees.Count == 0) {
72        IFunctionTree newTerminal = ChangeTerminalType(parent, selectedChild, selectedChildIndex, gardener, random);
73
74        if (parent == null) {
75          // no parent means the new child is the initial operator
76          // and we have to update the value in the variable
77          scope.GetVariable(scope.TranslateName("FunctionTree")).Value = newTerminal;
78        } else {
79          parent.RemoveSubTree(selectedChildIndex);
80          parent.InsertSubTree(selectedChildIndex, newTerminal);
81          // updating the variable is not necessary because it stays the same
82        }
83        if(!gardener.IsValidTree(root)) throw new InvalidProgramException();
84
85        // size and height stays the same when changing a terminal so no need to update the variables
86        // schedule an operation to initialize the new terminal
87        return gardener.CreateInitializationOperation(gardener.GetAllSubTrees(newTerminal), scope);
88      } else {
89        List<IFunctionTree> uninitializedBranches;
90        IFunctionTree newFunction = ChangeFunctionType(parent, selectedChild, selectedChildIndex, gardener, random, balancedTreesRate, out uninitializedBranches);
91
92        if (parent == null) {
93          // no parent means the new function is the initial operator
94          // and we have to update the value in the variable
95          scope.GetVariable(scope.TranslateName("FunctionTree")).Value = newFunction;
96          root = newFunction;
97        } else {
98          // remove the old child
99          parent.RemoveSubTree(selectedChildIndex);
100          // add the new child as sub-tree of parent
101          parent.InsertSubTree(selectedChildIndex, newFunction);
102        }
103
104        // recalculate size and height
105        treeSize.Data = gardener.GetTreeSize(root);
106        treeHeight.Data = gardener.GetTreeHeight(root);
107
108        // check if whole tree is ok
109        // check if the size of the new tree is still in the allowed bounds
110        if (!gardener.IsValidTree(root) ||
111          treeHeight.Data > maxTreeHeight ||
112          treeSize.Data > maxTreeSize) {
113          throw new InvalidProgramException();
114        }
115
116        // return a composite operation that initializes all created sub-trees
117        return gardener.CreateInitializationOperation(uninitializedBranches, scope);
118      }
119    }
120
121    private IFunctionTree ChangeTerminalType(IFunctionTree parent, IFunctionTree child, int childIndex, TreeGardener gardener, MersenneTwister random) {
122      IList<IFunction> allowedChildren;
123      if (parent == null) {
124        allowedChildren = gardener.Terminals;
125      } else {
126        allowedChildren = new List<IFunction>();
127        var allAllowedChildren = gardener.GetAllowedSubFunctions(parent.Function, childIndex);
128        foreach(IFunction c in allAllowedChildren) {
129          if(gardener.IsTerminal(c)) allowedChildren.Add(c);
130        }
131      }
132
133      // selecting from the terminals should always work since the current child was also a terminal
134      // so in the worst case we will just create a new terminal of the same type again.
135      return gardener.CreateRandomTree(allowedChildren[random.Next(allowedChildren.Count)], 1, 1, false);
136    }
137
138    private IFunctionTree ChangeFunctionType(IFunctionTree parent, IFunctionTree child, int childIndex, TreeGardener gardener, MersenneTwister random,
139      double balancedTreesRate, out List<IFunctionTree> uninitializedBranches) {
140      // since there are subtrees, we have to check which
141      // and how many of the existing subtrees we can reuse
142
143      // let's choose the function we want to use instead of the old child. For this we have to determine the
144      // pool of allowed functions based on constraints of the parent if there is one.
145      IList<IFunction> allowedFunctions = gardener.GetAllowedSubFunctions(parent!=null?parent.Function:null, childIndex);
146
147      // try to make a tree with the same arity as the old child.
148      int actualArity = child.SubTrees.Count;
149      // arity of the selected operator
150      int minArity;
151      int maxArity;
152      // only allow functions where we can keep all existing sub-trees
153      // we don't want to create new sub-trees here
154      // this restriction can be removed if we add code that creates sub-trees where necessary (gkronber 22.04.08)
155      allowedFunctions = allowedFunctions.Where(f => {
156        gardener.GetMinMaxArity(f, out minArity, out maxArity);
157        return minArity <= actualArity;
158      }).ToList();
159
160      // create a new tree-node for a randomly selected function
161      IFunctionTree newTree = new FunctionTree(allowedFunctions[random.Next(allowedFunctions.Count)]);
162
163      gardener.GetMinMaxArity(newTree.Function, out minArity, out maxArity);
164      // if the old child had too many sub-trees then the new child should keep as many sub-trees as possible
165      if (actualArity > maxArity)
166        actualArity = maxArity;
167
168      // get the allowed size and height for new sub-trees
169      // use the size of the smallest subtree as the maximal allowed size for new subtrees to
170      // prevent that we ever create trees over the MaxTreeSize limit
171      int maxSubTreeSize = child.SubTrees.Select(subTree => gardener.GetTreeSize(subTree)).Min();
172      int maxSubTreeHeight = gardener.GetTreeHeight(child) - 1;
173
174      // create a list that holds old sub-trees that we can reuse in the new tree
175      List<IFunctionTree> availableSubTrees = new List<IFunctionTree>(child.SubTrees);
176      List<IFunctionTree> freshSubTrees = new List<IFunctionTree>() { newTree };
177
178      // randomly select the sub-trees that we keep
179      for (int i = 0; i < actualArity; i++) {
180        // fill all sub-tree slots of the new tree
181        // if for a given slot i there are multiple existing sub-trees that can be used in that slot
182        // then use a random existing sub-tree. When there are no existing sub-trees
183        // that fit in the given slot then create a new random tree and use it for the slot
184        ICollection<IFunction> allowedSubFunctions = gardener.GetAllowedSubFunctions(newTree.Function, i);
185        var matchingSubTrees = availableSubTrees.Where(subTree => allowedSubFunctions.Contains(subTree.Function));
186
187        if (matchingSubTrees.Count() > 0) {
188          IFunctionTree selectedSubTree = matchingSubTrees.ElementAt(random.Next(matchingSubTrees.Count()));
189          // we can just add it as subtree
190          newTree.InsertSubTree(i, selectedSubTree);
191          availableSubTrees.Remove(selectedSubTree); // the branch shouldn't be available for the following slots
192        } else {
193          // no existing matching tree found => create a new one
194          IFunctionTree freshTree;
195          if(random.NextDouble() <= balancedTreesRate) {
196            freshTree = gardener.CreateRandomTree(allowedSubFunctions, maxSubTreeSize, maxSubTreeHeight, true);
197          } else {
198            freshTree = gardener.CreateRandomTree(allowedSubFunctions, maxSubTreeSize, maxSubTreeHeight, false);
199          }
200          freshSubTrees.AddRange(gardener.GetAllSubTrees(freshTree));
201
202          newTree.InsertSubTree(i, freshTree);
203        }
204      }
205      uninitializedBranches = freshSubTrees;
206      return newTree;
207    }
208  }
209}
Note: See TracBrowser for help on using the repository browser.