Free cookie consent management tool by TermsFeed Policy Generator

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

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

minor refactoring in TreeGardener and related classes

File size: 18.2 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    internal TreeGardener(IRandom random, GPOperatorLibrary funLibrary) {
53      this.random = random;
54      this.funLibrary = funLibrary;
55      this.allFunctions = new List<IFunction>();
56      terminals = new List<IFunction>();
57      functions = new List<IFunction>();
58      // init functions and terminals based on constraints
59      foreach(IFunction fun in funLibrary.Group.Operators) {
60        int maxA, minA;
61        GetMinMaxArity(fun, out minA, out maxA);
62        if(maxA == 0) {
63          terminals.Add(fun);
64          allFunctions.Add(fun);
65        } else {
66          functions.Add(fun);
67          allFunctions.Add(fun);
68        }
69      }
70    }
71
72    #region random initialization
73    internal IFunctionTree CreateBalancedRandomTree(int maxTreeSize, int maxTreeHeight) {
74      if(maxTreeHeight == 1 || maxTreeSize == 1) {
75        IFunction selectedTerminal = terminals[random.Next(terminals.Count())];
76        return new FunctionTree(selectedTerminal);
77      } else {
78        IFunction[] possibleFunctions = functions.Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
79          GetMinimalTreeSize(f) <= maxTreeSize).ToArray();
80        IFunction selectedFunction = possibleFunctions[random.Next(possibleFunctions.Length)];
81        FunctionTree root = new FunctionTree(selectedFunction);
82        MakeBalancedTree(root, maxTreeSize - 1, maxTreeHeight - 1);
83        return root;
84      }
85    }
86
87    internal IFunctionTree CreateUnbalancedRandomTree(int maxTreeSize, int maxTreeHeight) {
88      IFunction[] possibleFunctions = allFunctions.Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
89        GetMinimalTreeSize(f) <= maxTreeSize).ToArray();
90      IFunction selectedFunction = possibleFunctions[random.Next(possibleFunctions.Length)];
91      FunctionTree root = new FunctionTree(selectedFunction);
92      MakeUnbalancedTree(root, maxTreeSize - 1, maxTreeHeight - 1);
93      return root;
94    }
95
96    internal IFunctionTree CreateRandomTree(ICollection<IFunction> allowedFunctions, int maxTreeSize, int maxTreeHeight) {
97      // default is non-balanced trees
98      return CreateRandomTree(allowedFunctions, maxTreeSize, maxTreeHeight, false);
99    }
100
101    internal IFunctionTree CreateRandomTree(ICollection<IFunction> allowedFunctions, int maxTreeSize, int maxTreeHeight, bool balanceTrees) {
102
103      int minTreeHeight = allowedFunctions.Select(f => ((IntData)f.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data).Min();
104      if(minTreeHeight > maxTreeHeight)
105        maxTreeHeight = minTreeHeight;
106
107      int minTreeSize = allowedFunctions.Select(f => ((IntData)f.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data).Min();
108      if(minTreeSize > maxTreeSize)
109        maxTreeSize = minTreeSize;
110
111      int treeHeight = random.Next(minTreeHeight, maxTreeHeight + 1);
112      int treeSize = random.Next(minTreeSize, maxTreeSize + 1);
113
114      IFunction[] possibleFunctions = allowedFunctions.Where(f => ((IntData)f.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data <= treeHeight &&
115        ((IntData)f.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data <= treeSize).ToArray();
116      IFunction selectedFunction = possibleFunctions[random.Next(possibleFunctions.Length)];
117
118      return CreateRandomTree(selectedFunction, treeSize, treeHeight, balanceTrees);
119    }
120
121    internal IFunctionTree CreateRandomTree(IFunction rootFunction, int maxTreeSize, int maxTreeHeight, bool balanceTrees) {
122      IFunctionTree root = new FunctionTree(rootFunction);
123      if(balanceTrees) {
124        MakeBalancedTree(root, maxTreeSize - 1, maxTreeHeight - 1);
125      } else {
126        MakeUnbalancedTree(root, maxTreeSize - 1, maxTreeHeight - 1);
127      }
128      if(GetTreeSize(root) > maxTreeSize ||
129         GetTreeHeight(root) > maxTreeHeight) {
130        throw new InvalidProgramException();
131      }
132      return root;
133    }
134
135    private void MakeUnbalancedTree(IFunctionTree parent, int maxTreeSize, int maxTreeHeight) {
136      if(maxTreeHeight == 0 || maxTreeSize == 0) return;
137      int minArity;
138      int maxArity;
139      GetMinMaxArity(parent.Function, out minArity, out maxArity);
140      if(maxArity >= maxTreeSize) {
141        maxArity = maxTreeSize;
142      }
143      int actualArity = random.Next(minArity, maxArity + 1);
144      if(actualArity > 0) {
145        int maxSubTreeSize = maxTreeSize / actualArity;
146        for(int i = 0; i < actualArity; i++) {
147          IFunction[] possibleFunctions = GetAllowedSubFunctions(parent.Function, i).Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
148            GetMinimalTreeSize(f) <= maxSubTreeSize).ToArray();
149          IFunction selectedFunction = possibleFunctions[random.Next(possibleFunctions.Length)];
150          FunctionTree newSubTree = new FunctionTree(selectedFunction);
151          MakeUnbalancedTree(newSubTree, maxSubTreeSize - 1, maxTreeHeight - 1);
152          parent.InsertSubTree(i, newSubTree);
153        }
154      }
155    }
156
157    // NOTE: this method doesn't build fully balanced trees because we have constraints on the
158    // types of possible sub-functions which can indirectly impose a limit for the depth of a given sub-tree
159    private void MakeBalancedTree(IFunctionTree parent, int maxTreeSize, int maxTreeHeight) {
160      if(maxTreeHeight == 0 || maxTreeSize == 0) return; // should never happen anyway
161      int minArity;
162      int maxArity;
163      GetMinMaxArity(parent.Function, out minArity, out maxArity);
164      if(maxArity >= maxTreeSize) {
165        maxArity = maxTreeSize;
166      }
167      int actualArity = random.Next(minArity, maxArity + 1);
168      if(actualArity > 0) {
169        int maxSubTreeSize = maxTreeSize / actualArity;
170        for(int i = 0; i < actualArity; i++) {
171          if(maxTreeHeight == 1 || maxSubTreeSize == 1) {
172            IFunction[] possibleTerminals = GetAllowedSubFunctions(parent.Function, i).Where(
173              f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
174              GetMinimalTreeSize(f) <= maxSubTreeSize &&
175              IsTerminal(f)).ToArray();
176            IFunction selectedTerminal = possibleTerminals[random.Next(possibleTerminals.Length)];
177            IFunctionTree newTree = new FunctionTree(selectedTerminal);
178            parent.InsertSubTree(i, newTree);
179          } else {
180            IFunction[] possibleFunctions = GetAllowedSubFunctions(parent.Function, i).Where(
181              f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
182              GetMinimalTreeSize(f) <= maxSubTreeSize &&
183              !IsTerminal(f)).ToArray();
184            IFunction selectedFunction = possibleFunctions[random.Next(possibleFunctions.Length)];
185            FunctionTree newTree = new FunctionTree(selectedFunction);
186            parent.InsertSubTree(i, newTree);
187            MakeBalancedTree(newTree, maxSubTreeSize - 1, maxTreeHeight - 1);
188          }
189        }
190      }
191    }
192
193    internal CompositeOperation CreateInitializationOperation(ICollection<IFunctionTree> trees, IScope scope) {
194      // needed for the parameter shaking operation
195      CompositeOperation initializationOperation = new CompositeOperation();
196      Scope tempScope = new Scope("Temp. initialization scope");
197
198      var parametricTrees = trees.Where(t => t.Function.GetVariable(GPOperatorLibrary.INITIALIZATION) != null);
199
200      foreach(IFunctionTree tree in parametricTrees) {
201        // enqueue an initialization operation for each operator with local variables
202        IOperator initialization = (IOperator)tree.Function.GetVariable(GPOperatorLibrary.INITIALIZATION).Value;
203        Scope initScope = new Scope();
204
205        // copy the local variables into a temporary scope used for initialization
206        foreach(IVariable variable in tree.LocalVariables) {
207          initScope.AddVariable(variable);
208        }
209
210        tempScope.AddSubScope(initScope);
211        initializationOperation.AddOperation(new AtomicOperation(initialization, initScope));
212      }
213
214      Scope backupScope = new Scope("backup");
215      foreach(Scope subScope in scope.SubScopes) {
216        backupScope.AddSubScope(subScope);
217      }
218
219      scope.AddSubScope(tempScope);
220      scope.AddSubScope(backupScope);
221
222      // add an operation to remove the temporary scopes       
223      initializationOperation.AddOperation(new AtomicOperation(new RightReducer(), scope));
224      return initializationOperation;
225    }
226    #endregion
227
228    #region tree information gathering
229    internal int GetTreeSize(IFunctionTree tree) {
230      return 1 + tree.SubTrees.Sum(f => GetTreeSize(f));
231    }
232
233    internal int GetTreeHeight(IFunctionTree tree) {
234      if(tree.SubTrees.Count == 0) return 1;
235      return 1 + tree.SubTrees.Max(f => GetTreeHeight(f));
236    }
237
238    internal IFunctionTree GetRandomParentNode(IFunctionTree tree) {
239      List<IFunctionTree> parentNodes = new List<IFunctionTree>();
240
241      // add null for the parent of the root node
242      parentNodes.Add(null);
243
244      TreeForEach(tree, delegate(IFunctionTree possibleParentNode) {
245        if(possibleParentNode.SubTrees.Count > 0) {
246          parentNodes.Add(possibleParentNode);
247        }
248      });
249
250      return parentNodes[random.Next(parentNodes.Count)];
251    }
252
253    internal ICollection<IFunctionTree> GetAllSubTrees(IFunctionTree root) {
254      List<IFunctionTree> allTrees = new List<IFunctionTree>();
255      TreeForEach(root, t => { allTrees.Add(t); });
256      return allTrees;
257    }
258
259    /// <summary>
260    /// returns the height level of branch in the tree
261    /// if the branch == tree => 1
262    /// if branch is in the sub-trees of tree => 2
263    /// ...
264    /// if branch is not found => -1
265    /// </summary>
266    /// <param name="tree">root of the function tree to process</param>
267    /// <param name="branch">branch that is searched in the tree</param>
268    /// <returns></returns>
269    internal int GetBranchLevel(IFunctionTree tree, IFunctionTree branch) {
270      return GetBranchLevelHelper(tree, branch, 1);
271    }
272
273    // 'tail-recursive' helper
274    private int GetBranchLevelHelper(IFunctionTree tree, IFunctionTree branch, int level) {
275      if(branch == tree) return level;
276
277      foreach(IFunctionTree subTree in tree.SubTrees) {
278        int result = GetBranchLevelHelper(subTree, branch, level + 1);
279        if(result != -1) return result;
280      }
281
282      return -1;
283    }
284
285    internal bool IsValidTree(IFunctionTree tree) {
286      foreach(IConstraint constraint in tree.Function.Constraints) {
287        if(constraint is NumberOfSubOperatorsConstraint) {
288          int max = ((NumberOfSubOperatorsConstraint)constraint).MaxOperators.Data;
289          int min = ((NumberOfSubOperatorsConstraint)constraint).MinOperators.Data;
290          if(tree.SubTrees.Count < min || tree.SubTrees.Count > max)
291            return false;
292        }
293      }
294      foreach(IFunctionTree subTree in tree.SubTrees) {
295        if(!IsValidTree(subTree)) return false;
296      }
297      return true;
298    }
299
300    // returns a random branch from the specified level in the tree
301    internal IFunctionTree GetRandomBranch(IFunctionTree tree, int level) {
302      if(level == 0) return tree;
303      List<IFunctionTree> branches = GetBranchesAtLevel(tree, level);
304      return branches[random.Next(branches.Count)];
305    }
306    #endregion
307
308    #region function information (arity, allowed childs and parents)
309    internal ICollection<IFunction> GetPossibleParents(List<IFunction> list) {
310      List<IFunction> result = new List<IFunction>();
311      foreach(IFunction f in functions) {
312        if(IsPossibleParent(f, list)) {
313          result.Add(f);
314        }
315      }
316      return result;
317    }
318
319    private bool IsPossibleParent(IFunction f, List<IFunction> children) {
320      int minArity;
321      int maxArity;
322      GetMinMaxArity(f, out minArity, out maxArity);
323
324      // note: we can't assume that the operators in the children list have different types!
325
326      // when the maxArity of this function is smaller than the list of operators that
327      // should be included as sub-operators then it can't be a parent
328      if(maxArity < children.Count()) {
329        return false;
330      }
331      int nSlots = Math.Max(minArity, children.Count);
332
333      SubOperatorsConstraintAnalyser analyzer = new SubOperatorsConstraintAnalyser();
334      analyzer.AllPossibleOperators = children.Cast<IOperator>().ToArray<IOperator>();
335
336      List<HashSet<IFunction>> slotSets = new List<HashSet<IFunction>>();
337
338      // we iterate through all slots for sub-trees and calculate the set of
339      // allowed functions for this slot.
340      // we only count those slots that can hold at least one of the children that we should combine
341      for(int slot = 0; slot < nSlots; slot++) {
342        HashSet<IFunction> functionSet = new HashSet<IFunction>(analyzer.GetAllowedOperators(f, slot).Cast<IFunction>());
343        if(functionSet.Count() > 0) {
344          slotSets.Add(functionSet);
345        }
346      }
347
348      // ok at the end of this operation we know how many slots of the parent can actually
349      // hold one of our children.
350      // if the number of slots is smaller than the number of children we can be sure that
351      // we can never combine all children as sub-trees of the function and thus the function
352      // can't be a parent.
353      if(slotSets.Count() < children.Count()) {
354        return false;
355      }
356
357      // finally we sort the sets by size and beginning from the first set select one
358      // function for the slot and thus remove it as possible sub-tree from the remaining sets.
359      // when we can successfully assign all available children to a slot the function is a valid parent
360      // when only a subset of all children can be assigned to slots the function is no valid parent
361      slotSets.Sort((p, q) => p.Count() - q.Count());
362
363      int assignments = 0;
364      for(int i = 0; i < slotSets.Count() - 1; i++) {
365        if(slotSets[i].Count > 0) {
366          IFunction selected = slotSets[i].ElementAt(0);
367          assignments++;
368          for(int j = i + 1; j < slotSets.Count(); j++) {
369            slotSets[j].Remove(selected);
370          }
371        }
372      }
373
374      // sanity check
375      if(assignments > children.Count) throw new InvalidProgramException();
376      return assignments == children.Count - 1;
377    }
378    internal IList<IFunction> GetAllowedParents(IFunction child, int childIndex) {
379      List<IFunction> parents = new List<IFunction>();
380      foreach(IFunction function in functions) {
381        ICollection<IFunction> allowedSubFunctions = GetAllowedSubFunctions(function, childIndex);
382        if(allowedSubFunctions.Contains(child)) {
383          parents.Add(function);
384        }
385      }
386      return parents;
387    }
388    internal bool IsTerminal(IFunction f) {
389      int minArity;
390      int maxArity;
391      GetMinMaxArity(f, out minArity, out maxArity);
392      return minArity == 0 && maxArity == 0;
393    }
394    internal IList<IFunction> GetAllowedSubFunctions(IFunction f, int index) {
395      if(f == null) {
396        return allFunctions;
397      } else {
398        ItemList slotList = (ItemList)f.GetVariable(GPOperatorLibrary.ALLOWED_SUBOPERATORS).Value;
399        List<IFunction> result = new List<IFunction>();
400        foreach(IFunction function in (ItemList)slotList[index]) {
401          result.Add(function);
402        }
403        return result;
404      }
405    }
406    internal void GetMinMaxArity(IFunction f, out int minArity, out int maxArity) {
407      foreach(IConstraint constraint in f.Constraints) {
408        NumberOfSubOperatorsConstraint theConstraint = constraint as NumberOfSubOperatorsConstraint;
409        if(theConstraint != null) {
410          minArity = theConstraint.MinOperators.Data;
411          maxArity = theConstraint.MaxOperators.Data;
412          return;
413        }
414      }
415      // the default arity is 2
416      minArity = 2;
417      maxArity = 2;
418    }
419    #endregion
420
421    #region private utility methods
422
423    private int GetMinimalTreeHeight(IOperator op) {
424      return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data;
425    }
426
427    private int GetMinimalTreeSize(IOperator op) {
428      return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data;
429    }
430
431    private void TreeForEach(IFunctionTree tree, Action<IFunctionTree> action) {
432      action(tree);
433      foreach(IFunctionTree subTree in tree.SubTrees) {
434        TreeForEach(subTree, action);
435      }
436    }
437
438    private List<IFunctionTree> GetBranchesAtLevel(IFunctionTree tree, int level) {
439      if(level == 1) return new List<IFunctionTree>(tree.SubTrees);
440
441      List<IFunctionTree> branches = new List<IFunctionTree>();
442      foreach(IFunctionTree subTree in tree.SubTrees) {
443        branches.AddRange(GetBranchesAtLevel(subTree, level - 1));
444      }
445      return branches;
446    }
447
448
449    #endregion
450
451  }
452}
Note: See TracBrowser for help on using the repository browser.