Free cookie consent management tool by TermsFeed Policy Generator

source: branches/Persistence Test/HeuristicLab.GP/3.3/TreeGardener.cs @ 3579

Last change on this file since 3579 was 2222, checked in by gkronber, 15 years ago

Merged changes from GP-refactoring branch back into the trunk #713.

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