Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.StructureIdentification/TreeGardener.cs @ 630

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

small improvement for crossover

File size: 20.8 KB
RevLine 
[2]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.Text;
25using HeuristicLab.Core;
26using HeuristicLab.Constraints;
27using System.Diagnostics;
28using HeuristicLab.Data;
29using System.Linq;
30using HeuristicLab.Random;
31using HeuristicLab.Operators;
32using HeuristicLab.Selection;
[155]33using HeuristicLab.Functions;
34using System.Collections;
[2]35
36namespace HeuristicLab.StructureIdentification {
37  internal class TreeGardener {
38    private IRandom random;
[155]39    private GPOperatorLibrary funLibrary;
40    private List<IFunction> functions;
[179]41
[155]42    private List<IFunction> terminals;
43    internal IList<IFunction> Terminals {
[179]44      get { return terminals; }
[2]45    }
[179]46
[155]47    private List<IFunction> allFunctions;
48    internal IList<IFunction> AllFunctions {
[179]49      get { return allFunctions; }
[2]50    }
51
[180]52    #region constructors
[155]53    internal TreeGardener(IRandom random, GPOperatorLibrary funLibrary) {
[2]54      this.random = random;
[155]55      this.funLibrary = funLibrary;
56      this.allFunctions = new List<IFunction>();
57      terminals = new List<IFunction>();
58      functions = new List<IFunction>();
[2]59      // init functions and terminals based on constraints
[179]60      foreach(IFunction fun in funLibrary.Group.Operators) {
[526]61        if(fun.MaxArity == 0) {
[155]62          terminals.Add(fun);
[179]63          allFunctions.Add(fun);
[2]64        } else {
[155]65          functions.Add(fun);
[179]66          allFunctions.Add(fun);
[2]67        }
68      }
69    }
[180]70    #endregion
[2]71
72    #region random initialization
[180]73    /// <summary>
74    /// Creates a random balanced tree with a maximal size and height. When the max-height or max-size are 1 it will return a random terminal.
75    /// In other cases it will return either a terminal (tree of size 1) or any other tree with a function in it's root (at least height 2).
76    /// </summary>
77    /// <param name="maxTreeSize">Maximal size of the tree (number of nodes).</param>
78    /// <param name="maxTreeHeight">Maximal height of the tree.</param>
79    /// <returns></returns>
[179]80    internal IFunctionTree CreateBalancedRandomTree(int maxTreeSize, int maxTreeHeight) {
[202]81      IFunction rootFunction = GetRandomRoot(maxTreeSize, maxTreeHeight);
[448]82      IFunctionTree tree = MakeBalancedTree(rootFunction, maxTreeHeight - 1);
[202]83      return tree;
[179]84    }
85
[180]86    /// <summary>
87    /// Creates a random (unbalanced) tree with a maximal size and height. When the max-height or max-size are 1 it will return a random terminal.
88    /// In other cases it will return either a terminal (tree of size 1) or any other tree with a function in it's root (at least height 2).
89    /// </summary>
90    /// <param name="maxTreeSize">Maximal size of the tree (number of nodes).</param>
91    /// <param name="maxTreeHeight">Maximal height of the tree.</param>
92    /// <returns></returns>
[179]93    internal IFunctionTree CreateUnbalancedRandomTree(int maxTreeSize, int maxTreeHeight) {
[202]94      IFunction rootFunction = GetRandomRoot(maxTreeSize, maxTreeHeight);
[448]95      IFunctionTree tree = MakeUnbalancedTree(rootFunction, maxTreeHeight - 1);
[202]96      return tree;
[179]97    }
98
[286]99    internal IFunctionTree PTC2(IRandom random, int size, int maxDepth) {
[449]100      return PTC2(random, GetRandomRoot(size, maxDepth), size, maxDepth);
101    }
102
103    internal IFunctionTree PTC2(IRandom random, IFunction rootF, int size, int maxDepth) {
[452]104      IFunctionTree root = rootF.GetTreeNode();
105      if(size <= 1 || maxDepth <= 1) return root;
[286]106      List<object[]> list = new List<object[]>();
107      int currentSize = 1;
[444]108      int totalListMinSize = 0;
[526]109      int minArity = root.Function.MinArity;
110      int maxArity = root.Function.MaxArity;
[286]111      if(maxArity >= size) {
112        maxArity = size;
113      }
114      int actualArity = random.Next(minArity, maxArity + 1);
[444]115      totalListMinSize += GetMinimalTreeSize(root.Function) - 1;
116      for(int i = 0; i < actualArity; i++) {
[286]117        // insert a dummy sub-tree and add the pending extension to the list
118        root.AddSubTree(null);
[444]119        list.Add(new object[] { root, i, 2 });
[286]120      }
121
[444]122      while(list.Count > 0 && totalListMinSize + currentSize < size) {
[286]123        int randomIndex = random.Next(list.Count);
124        object[] nextExtension = list[randomIndex];
125        list.RemoveAt(randomIndex);
126        IFunctionTree parent = (IFunctionTree)nextExtension[0];
127        int a = (int)nextExtension[1];
128        int d = (int)nextExtension[2];
129        if(d == maxDepth) {
130          parent.RemoveSubTree(a);
[444]131          IFunctionTree branch = CreateRandomTree(GetAllowedSubFunctions(parent.Function, a), 1, 1);
132          parent.InsertSubTree(a, branch); // insert a smallest possible tree
133          currentSize += branch.Size;
[448]134          totalListMinSize -= branch.Size;
[286]135        } else {
[444]136          IFunction selectedFunction = RandomSelect(GetAllowedSubFunctions(parent.Function, a).Where(
137            f => !IsTerminal(f) && GetMinimalTreeHeight(f) + (d - 1) <= maxDepth).ToArray());
[286]138          IFunctionTree newTree = selectedFunction.GetTreeNode();
139          parent.RemoveSubTree(a);
140          parent.InsertSubTree(a, newTree);
[444]141          currentSize++;
142          totalListMinSize--;
[286]143
[526]144          minArity = selectedFunction.MinArity;
145          maxArity = selectedFunction.MaxArity;
[286]146          if(maxArity >= size) {
147            maxArity = size;
148          }
149          actualArity = random.Next(minArity, maxArity + 1);
150          for(int i = 0; i < actualArity; i++) {
151            // insert a dummy sub-tree and add the pending extension to the list
152            newTree.AddSubTree(null);
153            list.Add(new object[] { newTree, i, d + 1 });
154          }
[444]155          totalListMinSize += GetMinimalTreeSize(newTree.Function) - 1;
[286]156        }
157      }
158      while(list.Count > 0) {
159        int randomIndex = random.Next(list.Count);
160        object[] nextExtension = list[randomIndex];
161        list.RemoveAt(randomIndex);
162        IFunctionTree parent = (IFunctionTree)nextExtension[0];
163        int a = (int)nextExtension[1];
164        int d = (int)nextExtension[2];
165        parent.RemoveSubTree(a);
[525]166        parent.InsertSubTree(a,
[452]167          CreateRandomTree(GetAllowedSubFunctions(parent.Function, a), 1, 1)); // append a tree with minimal possible height
[286]168      }
169      return root;
170    }
171
[180]172    /// <summary>
173    /// selects a random function from allowedFunctions and creates a random (unbalanced) tree with maximal size and height.
174    /// </summary>
175    /// <param name="allowedFunctions">Set of allowed functions.</param>
176    /// <param name="maxTreeSize">Maximal size of the tree (number of nodes).</param>
177    /// <param name="maxTreeHeight">Maximal height of the tree.</param>
178    /// <returns>New random unbalanced tree</returns>
[163]179    internal IFunctionTree CreateRandomTree(ICollection<IFunction> allowedFunctions, int maxTreeSize, int maxTreeHeight) {
[181]180      // get the minimal needed height based on allowed functions and extend the max-height if necessary
[448]181      int minTreeHeight = allowedFunctions.Select(f => GetMinimalTreeHeight(f)).Min();
[179]182      if(minTreeHeight > maxTreeHeight)
[2]183        maxTreeHeight = minTreeHeight;
[181]184      // get the minimal needed size based on allowed functions and extend the max-size if necessary
[444]185      int minTreeSize = allowedFunctions.Select(f => GetMinimalTreeSize(f)).Min();
[179]186      if(minTreeSize > maxTreeSize)
[2]187        maxTreeSize = minTreeSize;
188
[181]189      // select a random value for the size and height
[2]190      int treeHeight = random.Next(minTreeHeight, maxTreeHeight + 1);
191      int treeSize = random.Next(minTreeSize, maxTreeSize + 1);
192
[181]193      // filter the set of allowed functions and select only from those that fit into the given maximal size and height limits
[444]194      IFunction[] possibleFunctions = allowedFunctions.Where(f => GetMinimalTreeHeight(f) <= treeHeight &&
195        GetMinimalTreeSize(f) <= treeSize).ToArray();
[182]196      IFunction selectedFunction = RandomSelect(possibleFunctions);
[2]197
[181]198      // build the tree
[202]199      IFunctionTree root;
[452]200      root = PTC2(random, selectedFunction, maxTreeSize, maxTreeHeight);
[2]201      return root;
202    }
203
[155]204    internal CompositeOperation CreateInitializationOperation(ICollection<IFunctionTree> trees, IScope scope) {
[2]205      // needed for the parameter shaking operation
206      CompositeOperation initializationOperation = new CompositeOperation();
207      Scope tempScope = new Scope("Temp. initialization scope");
208
[429]209      var parametricTrees = trees.Where(t => t.Function.GetVariable(FunctionBase.INITIALIZATION) != null);
[179]210      foreach(IFunctionTree tree in parametricTrees) {
[2]211        // enqueue an initialization operation for each operator with local variables
[429]212        IOperator initialization = (IOperator)tree.Function.GetVariable(FunctionBase.INITIALIZATION).Value;
[2]213        Scope initScope = new Scope();
214        // copy the local variables into a temporary scope used for initialization
[179]215        foreach(IVariable variable in tree.LocalVariables) {
[155]216          initScope.AddVariable(variable);
[2]217        }
218        tempScope.AddSubScope(initScope);
219        initializationOperation.AddOperation(new AtomicOperation(initialization, initScope));
220      }
221      Scope backupScope = new Scope("backup");
[179]222      foreach(Scope subScope in scope.SubScopes) {
[2]223        backupScope.AddSubScope(subScope);
224      }
225      scope.AddSubScope(tempScope);
226      scope.AddSubScope(backupScope);
227      // add an operation to remove the temporary scopes       
228      initializationOperation.AddOperation(new AtomicOperation(new RightReducer(), scope));
229      return initializationOperation;
230    }
231    #endregion
232
233    #region tree information gathering
[155]234    internal IFunctionTree GetRandomParentNode(IFunctionTree tree) {
235      List<IFunctionTree> parentNodes = new List<IFunctionTree>();
[2]236
237      // add null for the parent of the root node
238      parentNodes.Add(null);
239
[155]240      TreeForEach(tree, delegate(IFunctionTree possibleParentNode) {
[179]241        if(possibleParentNode.SubTrees.Count > 0) {
[155]242          parentNodes.Add(possibleParentNode);
[2]243        }
244      });
245
246      return parentNodes[random.Next(parentNodes.Count)];
247    }
248
[155]249    internal ICollection<IFunctionTree> GetAllSubTrees(IFunctionTree root) {
250      List<IFunctionTree> allTrees = new List<IFunctionTree>();
251      TreeForEach(root, t => { allTrees.Add(t); });
252      return allTrees;
[2]253    }
254
255    /// <summary>
[155]256    /// returns the height level of branch in the tree
257    /// if the branch == tree => 1
258    /// if branch is in the sub-trees of tree => 2
[2]259    /// ...
[155]260    /// if branch is not found => -1
[2]261    /// </summary>
[155]262    /// <param name="tree">root of the function tree to process</param>
263    /// <param name="branch">branch that is searched in the tree</param>
[2]264    /// <returns></returns>
[155]265    internal int GetBranchLevel(IFunctionTree tree, IFunctionTree branch) {
266      return GetBranchLevelHelper(tree, branch, 1);
[2]267    }
268
[155]269    // 'tail-recursive' helper
270    private int GetBranchLevelHelper(IFunctionTree tree, IFunctionTree branch, int level) {
[179]271      if(branch == tree) return level;
[2]272
[179]273      foreach(IFunctionTree subTree in tree.SubTrees) {
[155]274        int result = GetBranchLevelHelper(subTree, branch, level + 1);
[179]275        if(result != -1) return result;
[2]276      }
277
278      return -1;
279    }
280
[155]281    internal bool IsValidTree(IFunctionTree tree) {
[451]282      for(int i = 0; i < tree.SubTrees.Count; i++) {
[525]283        if(!tree.Function.AllowedSubFunctions(i).Contains(tree.SubTrees[i].Function)) return false;
[451]284      }
285
[525]286      if(tree.SubTrees.Count < tree.Function.MinArity || tree.SubTrees.Count > tree.Function.MaxArity)
287        return false;
[155]288      foreach(IFunctionTree subTree in tree.SubTrees) {
289        if(!IsValidTree(subTree)) return false;
290      }
[2]291      return true;
292    }
293
[155]294    // returns a random branch from the specified level in the tree
295    internal IFunctionTree GetRandomBranch(IFunctionTree tree, int level) {
[179]296      if(level == 0) return tree;
[527]297      List<IFunctionTree> branches = new List<IFunctionTree>();
298      GetBranchesAtLevel(tree, level, branches);
[155]299      return branches[random.Next(branches.Count)];
[2]300    }
301    #endregion
302
[179]303    #region function information (arity, allowed childs and parents)
[155]304    internal ICollection<IFunction> GetPossibleParents(List<IFunction> list) {
305      List<IFunction> result = new List<IFunction>();
[179]306      foreach(IFunction f in functions) {
307        if(IsPossibleParent(f, list)) {
[155]308          result.Add(f);
[2]309        }
310      }
311      return result;
312    }
313
[155]314    private bool IsPossibleParent(IFunction f, List<IFunction> children) {
[526]315      int minArity = f.MinArity;
316      int maxArity = f.MaxArity;
[2]317      // note: we can't assume that the operators in the children list have different types!
318
319      // when the maxArity of this function is smaller than the list of operators that
320      // should be included as sub-operators then it can't be a parent
[179]321      if(maxArity < children.Count()) {
[2]322        return false;
323      }
324      int nSlots = Math.Max(minArity, children.Count);
325
[155]326      List<HashSet<IFunction>> slotSets = new List<HashSet<IFunction>>();
[2]327
[155]328      // we iterate through all slots for sub-trees and calculate the set of
329      // allowed functions for this slot.
[2]330      // we only count those slots that can hold at least one of the children that we should combine
[179]331      for(int slot = 0; slot < nSlots; slot++) {
[525]332        HashSet<IFunction> functionSet = new HashSet<IFunction>(f.AllowedSubFunctions(slot));
[179]333        if(functionSet.Count() > 0) {
[155]334          slotSets.Add(functionSet);
[2]335        }
336      }
337
338      // ok at the end of this operation we know how many slots of the parent can actually
339      // hold one of our children.
340      // if the number of slots is smaller than the number of children we can be sure that
[155]341      // we can never combine all children as sub-trees of the function and thus the function
[2]342      // can't be a parent.
[179]343      if(slotSets.Count() < children.Count()) {
[2]344        return false;
345      }
346
347      // finally we sort the sets by size and beginning from the first set select one
[155]348      // function for the slot and thus remove it as possible sub-tree from the remaining sets.
349      // when we can successfully assign all available children to a slot the function is a valid parent
350      // when only a subset of all children can be assigned to slots the function is no valid parent
[2]351      slotSets.Sort((p, q) => p.Count() - q.Count());
352
353      int assignments = 0;
[179]354      for(int i = 0; i < slotSets.Count() - 1; i++) {
355        if(slotSets[i].Count > 0) {
[155]356          IFunction selected = slotSets[i].ElementAt(0);
[2]357          assignments++;
[179]358          for(int j = i + 1; j < slotSets.Count(); j++) {
[2]359            slotSets[j].Remove(selected);
360          }
361        }
362      }
363
364      // sanity check
[179]365      if(assignments > children.Count) throw new InvalidProgramException();
[2]366      return assignments == children.Count - 1;
367    }
[179]368    internal IList<IFunction> GetAllowedParents(IFunction child, int childIndex) {
369      List<IFunction> parents = new List<IFunction>();
370      foreach(IFunction function in functions) {
371        ICollection<IFunction> allowedSubFunctions = GetAllowedSubFunctions(function, childIndex);
372        if(allowedSubFunctions.Contains(child)) {
373          parents.Add(function);
374        }
375      }
376      return parents;
377    }
378    internal bool IsTerminal(IFunction f) {
[526]379      return f.MinArity == 0 && f.MaxArity == 0;
[179]380    }
381    internal IList<IFunction> GetAllowedSubFunctions(IFunction f, int index) {
382      if(f == null) {
383        return allFunctions;
384      } else {
[525]385        return f.AllowedSubFunctions(index);
[179]386      }
387    }
388    #endregion
389
390    #region private utility methods
[202]391    private IFunction GetRandomRoot(int maxTreeSize, int maxTreeHeight) {
[180]392      if(maxTreeHeight == 1 || maxTreeSize == 1) {
[182]393        IFunction selectedTerminal = RandomSelect(terminals);
[202]394        return selectedTerminal;
[180]395      } else {
[284]396        IFunction[] possibleFunctions = functions.Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
[180]397          GetMinimalTreeSize(f) <= maxTreeSize).ToArray();
[182]398        IFunction selectedFunction = RandomSelect(possibleFunctions);
[202]399        return selectedFunction;
[180]400      }
401    }
[179]402
[448]403
404    private IFunctionTree MakeUnbalancedTree(IFunction parent, int maxTreeHeight) {
405      if(maxTreeHeight == 0) return parent.GetTreeNode();
[526]406      int minArity = parent.MinArity;
407      int maxArity = parent.MaxArity;
[180]408      int actualArity = random.Next(minArity, maxArity + 1);
409      if(actualArity > 0) {
[202]410        IFunctionTree parentTree = parent.GetTreeNode();
[180]411        for(int i = 0; i < actualArity; i++) {
[448]412          IFunction[] possibleFunctions = GetAllowedSubFunctions(parent, i).Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight).ToArray();
[182]413          IFunction selectedFunction = RandomSelect(possibleFunctions);
[448]414          IFunctionTree newSubTree = MakeUnbalancedTree(selectedFunction, maxTreeHeight - 1);
[202]415          parentTree.InsertSubTree(i, newSubTree);
[180]416        }
[202]417        return parentTree;
[180]418      }
[202]419      return parent.GetTreeNode();
[180]420    }
421
[448]422
[180]423    // NOTE: this method doesn't build fully balanced trees because we have constraints on the
424    // types of possible sub-functions which can indirectly impose a limit for the depth of a given sub-tree
[448]425    private IFunctionTree MakeBalancedTree(IFunction parent, int maxTreeHeight) {
426      if(maxTreeHeight == 0) return parent.GetTreeNode();
[526]427      int minArity = parent.MinArity;
428      int maxArity = parent.MaxArity;
[180]429      int actualArity = random.Next(minArity, maxArity + 1);
430      if(actualArity > 0) {
[202]431        IFunctionTree parentTree = parent.GetTreeNode();
[180]432        for(int i = 0; i < actualArity; i++) {
[448]433          // first try to find a function that fits into the maxHeight limit
[450]434          IFunction[] possibleFunctions = GetAllowedSubFunctions(parent, i).Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
[199]435            !IsTerminal(f)).ToArray();
436          // no possible function found => extend function set to terminals
437          if(possibleFunctions.Length == 0) {
[202]438            possibleFunctions = GetAllowedSubFunctions(parent, i).Where(f => IsTerminal(f)).ToArray();
[199]439            IFunction selectedTerminal = RandomSelect(possibleFunctions);
[189]440            IFunctionTree newTree = selectedTerminal.GetTreeNode();
[202]441            parentTree.InsertSubTree(i, newTree);
[180]442          } else {
[182]443            IFunction selectedFunction = RandomSelect(possibleFunctions);
[448]444            IFunctionTree newTree = MakeBalancedTree(selectedFunction, maxTreeHeight - 1);
[202]445            parentTree.InsertSubTree(i, newTree);
[180]446          }
447        }
[202]448        return parentTree;
[180]449      }
[202]450      return parent.GetTreeNode();
[180]451    }
452
[179]453    private int GetMinimalTreeHeight(IOperator op) {
454      return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data;
455    }
456
457    private int GetMinimalTreeSize(IOperator op) {
458      return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data;
459    }
460
461    private void TreeForEach(IFunctionTree tree, Action<IFunctionTree> action) {
462      action(tree);
463      foreach(IFunctionTree subTree in tree.SubTrees) {
464        TreeForEach(subTree, action);
465      }
466    }
467
[527]468    private void GetBranchesAtLevel(IFunctionTree tree, int level, List<IFunctionTree> result) {
469      if(level == 1) result.AddRange(tree.SubTrees);
[179]470      foreach(IFunctionTree subTree in tree.SubTrees) {
[444]471        if(subTree.Height >= level - 1)
[527]472          GetBranchesAtLevel(subTree, level - 1, result);
[179]473      }
474    }
475
[182]476    private IFunction RandomSelect(IList<IFunction> functionSet) {
477      double[] accumulatedTickets = new double[functionSet.Count];
478      double ticketAccumulator = 0;
479      int i = 0;
480      // precalculate the slot-sizes
481      foreach(IFunction function in functionSet) {
482        ticketAccumulator += ((DoubleData)function.GetVariable(GPOperatorLibrary.TICKETS).Value).Data;
483        accumulatedTickets[i] = ticketAccumulator;
484        i++;
485      }
486      // throw ball
487      double r = random.NextDouble() * ticketAccumulator;
488      // find the slot that has been hit
489      for(i = 0; i < accumulatedTickets.Length; i++) {
490        if(r < accumulatedTickets[i]) return functionSet[i];
491      }
492      // sanity check
493      throw new InvalidProgramException(); // should never happen
494    }
[179]495
496    #endregion
497
[2]498  }
499}
Note: See TracBrowser for help on using the repository browser.