Free cookie consent management tool by TermsFeed Policy Generator

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

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

removed size limit check in MakeBalancedTree and MakeUnbalancedTree. The methods are used for classic RampedHalfHalfInitialization where only the height constraint is checked. Use PTC2 initialization if the size constraint should also be checked. (ticket #225)

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