Free cookie consent management tool by TermsFeed Policy Generator

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

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

fixed #227

File size: 23.9 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, maxTreeSize - 1, 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, maxTreeSize - 1, 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 => GetMinimalTreeSize(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, maxTreeSize - 1, maxTreeHeight - 1);
215      } else {
216        root = MakeUnbalancedTree(selectedFunction, maxTreeSize - 1, 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    private IFunctionTree MakeUnbalancedTree(IFunction parent, int maxTreeSize, int maxTreeHeight) {
449      if(maxTreeHeight == 0 || maxTreeSize == 0) return parent.GetTreeNode();
450      int minArity;
451      int maxArity;
452      GetMinMaxArity(parent, out minArity, out maxArity);
453      if(maxArity >= maxTreeSize) {
454        maxArity = maxTreeSize;
455      }
456      int actualArity = random.Next(minArity, maxArity + 1);
457      if(actualArity > 0) {
458        IFunctionTree parentTree = parent.GetTreeNode();
459        int maxSubTreeSize = maxTreeSize / actualArity;
460        for(int i = 0; i < actualArity; i++) {
461          IFunction[] possibleFunctions = GetAllowedSubFunctions(parent, i).Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
462            GetMinimalTreeSize(f) <= maxSubTreeSize).ToArray();
463          IFunction selectedFunction = RandomSelect(possibleFunctions);
464          IFunctionTree newSubTree = MakeUnbalancedTree(selectedFunction, maxSubTreeSize - 1, maxTreeHeight - 1);
465          parentTree.InsertSubTree(i, newSubTree);
466        }
467        return parentTree;
468      }
469      return parent.GetTreeNode();
470    }
471
472    // NOTE: this method doesn't build fully balanced trees because we have constraints on the
473    // types of possible sub-functions which can indirectly impose a limit for the depth of a given sub-tree
474    private IFunctionTree MakeBalancedTree(IFunction parent, int maxTreeSize, int maxTreeHeight) {
475      if(maxTreeHeight == 0 || maxTreeSize == 0) return parent.GetTreeNode();
476      int minArity;
477      int maxArity;
478      GetMinMaxArity(parent, out minArity, out maxArity);
479      if(maxArity >= maxTreeSize) {
480        maxArity = maxTreeSize;
481      }
482      int actualArity = random.Next(minArity, maxArity + 1);
483      if(actualArity > 0) {
484        IFunctionTree parentTree = parent.GetTreeNode();
485        int maxSubTreeSize = maxTreeSize / actualArity;
486        for(int i = 0; i < actualArity; i++) {
487          // first try to find a function that fits into the maxHeight and maxSize limits
488          IFunction[] possibleFunctions = GetAllowedSubFunctions(parent, i).Where(
489            f => GetMinimalTreeHeight(f) <= maxTreeHeight &&
490            GetMinimalTreeSize(f) <= maxSubTreeSize &&
491            !IsTerminal(f)).ToArray();
492          // no possible function found => extend function set to terminals
493          if(possibleFunctions.Length == 0) {
494            possibleFunctions = GetAllowedSubFunctions(parent, i).Where(f => IsTerminal(f)).ToArray();
495            IFunction selectedTerminal = RandomSelect(possibleFunctions);
496            IFunctionTree newTree = selectedTerminal.GetTreeNode();
497            parentTree.InsertSubTree(i, newTree);
498          } else {
499            IFunction selectedFunction = RandomSelect(possibleFunctions);
500            IFunctionTree newTree = MakeBalancedTree(selectedFunction, maxSubTreeSize - 1, maxTreeHeight - 1);
501            parentTree.InsertSubTree(i, newTree);
502          }
503        }
504        return parentTree;
505      }
506      return parent.GetTreeNode();
507    }
508
509    private int GetMinimalTreeHeight(IOperator op) {
510      return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data;
511    }
512
513    private int GetMinimalTreeSize(IOperator op) {
514      return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data;
515    }
516
517    private void TreeForEach(IFunctionTree tree, Action<IFunctionTree> action) {
518      action(tree);
519      foreach(IFunctionTree subTree in tree.SubTrees) {
520        TreeForEach(subTree, action);
521      }
522    }
523
524    private List<IFunctionTree> GetBranchesAtLevel(IFunctionTree tree, int level) {
525      if(level == 1) return new List<IFunctionTree>(tree.SubTrees);
526
527      List<IFunctionTree> branches = new List<IFunctionTree>();
528      foreach(IFunctionTree subTree in tree.SubTrees) {
529        if(subTree.Height >= level - 1)
530          branches.AddRange(GetBranchesAtLevel(subTree, level - 1));
531      }
532      return branches;
533    }
534
535    private IFunction RandomSelect(IList<IFunction> functionSet) {
536      double[] accumulatedTickets = new double[functionSet.Count];
537      double ticketAccumulator = 0;
538      int i = 0;
539      // precalculate the slot-sizes
540      foreach(IFunction function in functionSet) {
541        ticketAccumulator += ((DoubleData)function.GetVariable(GPOperatorLibrary.TICKETS).Value).Data;
542        accumulatedTickets[i] = ticketAccumulator;
543        i++;
544      }
545      // throw ball
546      double r = random.NextDouble() * ticketAccumulator;
547      // find the slot that has been hit
548      for(i = 0; i < accumulatedTickets.Length; i++) {
549        if(r < accumulatedTickets[i]) return functionSet[i];
550      }
551      // sanity check
552      throw new InvalidProgramException(); // should never happen
553    }
554
555    #endregion
556
557  }
558}
Note: See TracBrowser for help on using the repository browser.