Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.EvolutionaryTracking/HeuristicLab.EvolutionaryTracking/3.4/Analyzers/SymbolicExpressionTreeGenealogyAnalyzer.cs @ 7515

Last change on this file since 7515 was 7515, checked in by bburlacu, 12 years ago

#1772: Added tracking for mutation operators.

File size: 24.0 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 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.Globalization;
25using System.Linq;
26using System.Text;
27using System.Threading;
28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.Data;
31using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
32using HeuristicLab.Operators;
33using HeuristicLab.Optimization;
34using HeuristicLab.Parameters;
35using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
36using HeuristicLab.Problems.DataAnalysis;
37using HeuristicLab.Problems.DataAnalysis.Symbolic;
38
39using CloneMapType = HeuristicLab.Core.ItemDictionary<HeuristicLab.Core.IItem, HeuristicLab.Core.IItem>;
40using TraceMapType = HeuristicLab.Core.ItemDictionary<HeuristicLab.Core.IItem, HeuristicLab.Core.IItemList<HeuristicLab.Core.IItem>>;
41
42namespace HeuristicLab.EvolutionaryTracking {
43  /// <summary>
44  /// An operator that tracks the genealogy of every individual
45  /// </summary>
46  [Item("SymbolicExpressionTreeGenealogyAnalyzer", "An operator that tracks tree lengths of Symbolic Expression Trees")]
47  [StorableClass]
48  public sealed class SymbolicExpressionTreeGenealogyAnalyzer : SingleSuccessorOperator, IAnalyzer {
49    private const string UpdateIntervalParameterName = "UpdateInterval";
50    private const string UpdateCounterParameterName = "UpdateCounter";
51    private const string ResultsParameterName = "Results";
52    private const string ElitesParameterName = "Elites";
53    private const string GenerationsParameterName = "Generations";
54    private const string MaximumGenerationsParameterName = "MaximumGenerations";
55    private const string MaximumSelectionPressureParameterName = "MaximumSelectionPressure";
56    private const string SelectionPressureParameterName = "SelectionPressure";
57    private const string GlobalTraceMapParameterName = "GlobalTraceMap";
58    private const string GlobalCloneMapParameterName = "GlobalCloneMap";
59    private const string PopulationGraphResultParameterName = "PopulationGraph";
60    private const string PopulationGraphResultParameterDescription = "Individual lineages";
61    private const string SymbolicExpressionInterpreterParameterName = "SymbolicExpressionTreeInterpreter";
62    private const string SymbolicRegressionProblemDataParameterName = "ProblemData";
63    private const string SymbolicDataAnalysisProblemEvaluatorParameterName = "Evaluator";
64
65    #region Parameter properties
66    public ValueParameter<IntValue> UpdateIntervalParameter {
67      get { return (ValueParameter<IntValue>)Parameters[UpdateIntervalParameterName]; }
68    }
69    public ValueParameter<IntValue> UpdateCounterParameter {
70      get { return (ValueParameter<IntValue>)Parameters[UpdateCounterParameterName]; }
71    }
72    public LookupParameter<ResultCollection> ResultsParameter {
73      get { return (LookupParameter<ResultCollection>)Parameters[ResultsParameterName]; }
74    }
75    public LookupParameter<IntValue> ElitesParameter {
76      get { return (LookupParameter<IntValue>)Parameters[ElitesParameterName]; }
77    }
78    public LookupParameter<IntValue> GenerationsParameter {
79      get { return (LookupParameter<IntValue>)Parameters[GenerationsParameterName]; }
80    }
81    public LookupParameter<IntValue> MaximumGenerationsParameter {
82      get { return (LookupParameter<IntValue>)Parameters[MaximumGenerationsParameterName]; }
83    }
84    public LookupParameter<DoubleValue> SelectionPressureParameter {
85      get { return (LookupParameter<DoubleValue>)Parameters[SelectionPressureParameterName]; }
86    }
87    public LookupParameter<DoubleValue> MaximumSelectionPressureParameter {
88      get { return (LookupParameter<DoubleValue>)Parameters[MaximumSelectionPressureParameterName]; }
89    }
90    // problem data, interpreter and evaluator
91    public LookupParameter<SymbolicDataAnalysisExpressionTreeInterpreter> SymbolicExpressionInterpreterParameter {
92      get { return (LookupParameter<SymbolicDataAnalysisExpressionTreeInterpreter>)Parameters[SymbolicExpressionInterpreterParameterName]; }
93    }
94    public LookupParameter<RegressionProblemData> SymbolicRegressionProblemDataParameter {
95      get { return (LookupParameter<RegressionProblemData>)Parameters[SymbolicRegressionProblemDataParameterName]; }
96    }
97    public LookupParameter<IEvaluator> SymbolicDataAnalysisProblemEvaluatorParameter {
98      get { return (LookupParameter<IEvaluator>)Parameters[SymbolicDataAnalysisProblemEvaluatorParameterName]; }
99    }
100    // genealogy global parameters
101    public LookupParameter<TraceMapType> GlobalTraceMapParameter {
102      get { return (LookupParameter<TraceMapType>)Parameters[GlobalTraceMapParameterName]; }
103    }
104    public LookupParameter<CloneMapType> GlobalCloneMapParameter {
105      get { return (LookupParameter<CloneMapType>)Parameters[GlobalCloneMapParameterName]; }
106    }
107    #endregion
108
109    #region Properties
110    public bool EnabledByDefault {
111      get { return true; }
112    }
113    public IntValue UpdateInterval {
114      get { return UpdateIntervalParameter.Value; }
115    }
116    public IntValue UpdateCounter {
117      get { return UpdateCounterParameter.Value; }
118    }
119    public ResultCollection Results {
120      get { return ResultsParameter.ActualValue; }
121    }
122    public IntValue Elites {
123      get { return ElitesParameter.ActualValue; }
124    }
125    public IntValue Generations {
126      get { return GenerationsParameter.ActualValue; }
127    }
128    public IntValue MaximumGenerations {
129      get { return MaximumGenerationsParameter.ActualValue; }
130    }
131    public DoubleValue SelectionPressure {
132      get { return SelectionPressureParameter.ActualValue; }
133    }
134    public DoubleValue MaximumSelectionPressure {
135      get { return MaximumSelectionPressureParameter.ActualValue; }
136    }
137    public CloneMapType GlobalCloneMap {
138      get { return GlobalCloneMapParameter.ActualValue; }
139    }
140    public TraceMapType GlobalTraceMap {
141      get { return GlobalTraceMapParameter.ActualValue; }
142    }
143    public SymbolicDataAnalysisExpressionTreeInterpreter SymbolicExpressionInterpreter {
144      get { return SymbolicExpressionInterpreterParameter.ActualValue; }
145    }
146    public RegressionProblemData SymbolicRegressionProblemData {
147      get { return SymbolicRegressionProblemDataParameter.ActualValue; }
148    }
149    public IEvaluator SymbolicDataAnalysisEvaluator {
150      get { return SymbolicDataAnalysisProblemEvaluatorParameter.ActualValue; }
151    }
152    #endregion
153
154    [StorableConstructor]
155    private SymbolicExpressionTreeGenealogyAnalyzer(bool deserializing)
156      : base() {
157    }
158    private SymbolicExpressionTreeGenealogyAnalyzer(SymbolicExpressionTreeGenealogyAnalyzer original, Cloner cloner)
159      : base(original, cloner) {
160    }
161    public override IDeepCloneable Clone(Cloner cloner) {
162      return new SymbolicExpressionTreeGenealogyAnalyzer(this, cloner);
163    }
164    public SymbolicExpressionTreeGenealogyAnalyzer()
165      : base() {
166      Parameters.Add(new LookupParameter<IntValue>(ElitesParameterName, "The number of elites."));
167      Parameters.Add(new LookupParameter<IntValue>(GenerationsParameterName, "The number of generations so far."));
168      Parameters.Add(new LookupParameter<IntValue>(MaximumGenerationsParameterName, "The maximum number of generations"));
169      Parameters.Add(new LookupParameter<DoubleValue>(SelectionPressureParameterName, "The current selection (ony for OSGA)"));
170      Parameters.Add(new LookupParameter<DoubleValue>(MaximumSelectionPressureParameterName, "Maximum allowed value of selection pressure."));
171      Parameters.Add(new ValueParameter<IntValue>(UpdateIntervalParameterName, "The interval in which the tree length analysis should be applied.", new IntValue(1)));
172      Parameters.Add(new ValueParameter<IntValue>(UpdateCounterParameterName, "The value which counts how many times the operator was called since the last update", new IntValue(0)));
173      Parameters.Add(new LookupParameter<TraceMapType>(GlobalTraceMapParameterName, "A global cache containing the whole genealogy."));
174      Parameters.Add(new LookupParameter<CloneMapType>(GlobalCloneMapParameterName, "A global map keeping track of trees and their clones (made during selection)."));
175      Parameters.Add(new ValueLookupParameter<ResultCollection>(ResultsParameterName, "The results collection where the analysis values should be stored."));
176      Parameters.Add(new LookupParameter<SymbolicDataAnalysisExpressionTreeInterpreter>(SymbolicExpressionInterpreterParameterName, "Interpreter for symbolic expression trees"));
177      Parameters.Add(new LookupParameter<RegressionProblemData>(SymbolicRegressionProblemDataParameterName, "The symbolic data analysis problem."));
178      Parameters.Add(new LookupParameter<IEvaluator>(SymbolicDataAnalysisProblemEvaluatorParameterName, "The fitness evaluator"));
179
180      UpdateCounterParameter.Hidden = true;
181      UpdateIntervalParameter.Hidden = true;
182    }
183
184    [StorableHook(HookType.AfterDeserialization)]
185    private void AfterDeserialization() {
186      // check if all the parameters are present and accounted for
187      if (!Parameters.ContainsKey(UpdateIntervalParameterName)) {
188        Parameters.Add(new ValueParameter<IntValue>(UpdateIntervalParameterName, "The interval in which the tree length analysis should be applied.", new IntValue(1)));
189      }
190      if (Parameters.ContainsKey(UpdateCounterParameterName)) return;
191      Parameters.Add(new ValueParameter<IntValue>(UpdateCounterParameterName, "The value which counts how many times the operator was called since the last update", new IntValue(0)));
192      UpdateCounterParameter.Hidden = true;
193    }
194
195    #region IStatefulItem members
196    public override void InitializeState() {
197      base.InitializeState();
198      UpdateCounter.Value = 0;
199    }
200
201    public override void ClearState() {
202      base.ClearState();
203      UpdateCounter.Value = 0;
204    }
205    #endregion
206
207    public override IOperation Apply() {
208      UpdateCounter.Value++;
209      // the analyzer runs periodically, every 'updateInterval' times
210      if (UpdateCounter.Value == UpdateInterval.Value) {
211        UpdateCounter.Value = 0; // reset counter
212
213        var gScope = ExecutionContext.Scope;
214        while (gScope.Parent != null) gScope = gScope.Parent;
215        GenealogyGraph graph;
216        if (!Results.ContainsKey(PopulationGraphResultParameterName)) {
217          graph = new GenealogyGraph();
218          Results.Add(new Result(PopulationGraphResultParameterName, PopulationGraphResultParameterDescription, graph));
219        } else {
220          graph = (GenealogyGraph)Results[PopulationGraphResultParameterName].Value;
221        }
222        // get tree quality values
223        var qualities = (from s in gScope.SubScopes
224                         let individual = s.Variables.First().Value
225                         let quality = (DoubleValue)s.Variables["Quality"].Value
226                         orderby quality.Value descending
227                         select new Tuple<IItem, double>(individual, quality.Value)).ToDictionary(t => t.Item1, t => t.Item2);
228
229        // add all individuals to the evolutionary graph
230        int generation = Generations.Value;
231        int count = GlobalTraceMap.Count;
232        string label;
233
234        if (generation == 0) {
235          // at generation 0 no trace map is present (since no reproduction has taken place yet),
236          // so we only add the initial trees as nodes in the genealogy graph
237          for (int i = 0; i != qualities.Count; ++i) {
238            var tree = qualities.ElementAt(i).Key;
239            label = (i + 1).ToString(CultureInfo.InvariantCulture);
240            graph.AddNode(tree, qualities[tree], label, generation);
241          }
242          return base.Apply();
243        }
244
245        // mark and add elites
246        // elites do not appear in the trace map (because they are never the product of a genetic operation)
247        var elites = qualities.OrderByDescending(x => x.Value).Take(Elites.Value).Select(x => x.Key).ToList();
248        for (int i = 0; i != Elites.Value; ++i) {
249          label = (generation * count + i + 1).ToString(CultureInfo.InvariantCulture);
250          var elite = elites[i];
251          if (!graph.HasNode(elite))
252            graph.AddNode(elite, qualities[elite], label, Generations.Value, true);
253          else
254            graph.GetNode(elite).Label += "\\n" + label;
255
256          graph.GetNode(elite).Color = new Color { R = 0, G = 100, B = 150 };
257        }
258
259        for (int i = 0; i != count; ++i) {
260          var trace = GlobalTraceMap.ElementAt(i);
261          var child = (ISymbolicExpressionTree)trace.Key;
262
263          if (!graph.HasNode(child)) {
264            // due to the structure of the trace map, qualities[child] will never throw an exception, so we use it directly
265            label = (generation * count + i + 1 + Elites.Value).ToString(CultureInfo.InvariantCulture);
266            graph.AddNode(child, qualities[child], label, generation);
267          }
268          var parents = trace.Value;
269          foreach (var parent in parents) {
270            if (!graph.HasNode(parent)) {
271              // if the node is a clone introduced pre-mutation, then its quality value has to be evaluated
272              if (!qualities.ContainsKey(parent))
273                qualities[parent] = Evaluate((ISymbolicExpressionTree)parent);
274              label = ((generation - 1) * count + i + 1).ToString(CultureInfo.InvariantCulture);
275              graph.AddNode(parent, qualities[parent], label, generation - 1);
276            }
277            graph.AddArc(parent, child);
278          }
279        }
280        GlobalTraceMap.Clear(); // no need to check for null here (see line 212)
281
282        // if we've reached the end of the run
283        bool maxGenerationsReached = (Generations.Value == MaximumGenerations.Value);
284        bool isOsga = (SelectionPressure != null && MaximumSelectionPressure != null);
285        bool maxSelectionPressureReached = isOsga && (SelectionPressure.Value >= MaximumSelectionPressure.Value);
286
287        #region end of the run
288        if (maxGenerationsReached || maxSelectionPressureReached) {
289          var path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
290
291          // write whole graph to a dot file
292          WriteDot(path + @"\lineage.dot", graph);
293
294          // get genealogy of the best solution
295          var bestSolution = (ISymbolicExpressionTree)qualities.First().Key;
296          var genealogy = graph.GetNode(bestSolution).Genealogy();
297          WriteDot(path + @"\bestlineage.dot", genealogy);
298
299          // write the direct root lineage of the best solution (is it useful?)
300
301          // calculate impact values of nodes in the best solution, attempt to trace those with high impact to their origins
302          //var impactValuesCalculator = new RegressionSolutionImpactValuesCalculator();
303          //var impactValues = impactValuesCalculator.CalculateImpactValues(bestSolution, SymbolicExpressionInterpreter, SymbolicRegressionProblemData);
304          ////var impactValues = CalculateImpactValues(bestSolution);
305          //foreach (var pair in impactValues.Where(pair => !(pair.Key is ConstantTreeNode || pair.Key is VariableTreeNode) && pair.Value > 0.9)) {
306          //  var node = pair.Key;
307
308          //  foreach (var ancestor in genealogy.Keys) {
309          //    graph.GetNode(ancestor).Color = ContainsSubtree(ancestor as ISymbolicExpressionTree, node) ? new Color { R = 0, G = 0, B = 150 } : new Color { R = 255, G = 255, B = 255 };
310          //  }
311          //}
312          //WriteDot(path + @"\impactancestry.dot", genealogy);
313
314          // trim the graph
315          // exclude the individuals of the last generation
316          var individuals = graph.Keys.Except(qualities.Select(x => x.Key)).ToList();
317          bool done = false;
318          while (!done) {
319            done = true;
320            foreach (var ind in individuals) {
321              // if node has no outgoing connections (absence of offspring), remove it from the graph
322              var node = graph.GetNode(ind);
323              if (node == null) continue;
324              if (node.OutEdges == null) {
325                done = false; // we still have "dead" nodes
326                graph.RemoveNode(ind);
327              }
328            }
329          }
330          WriteDot(path + @"\trimmedlineage.dot", graph);
331        }
332        #endregion
333      }
334      return base.Apply();
335    }
336
337    private double Evaluate(ISymbolicExpressionTree tree) {
338      // we perform evaluation by adding a temporary subscope with the tree in it, and calling evaluator.Apply()
339      var subScope = new Scope();
340      // inject expected variables into the subscope
341      subScope.Variables.Add(new Core.Variable("SymbolicExpressionTree", tree));
342      ExecutionContext.Scope.SubScopes.Add(subScope);
343      var context = new Core.ExecutionContext(ExecutionContext, SymbolicDataAnalysisEvaluator, subScope);
344      SymbolicDataAnalysisEvaluator.Execute(context, new CancellationToken());
345      // get the quality
346      double quality = ((DoubleValue)subScope.Variables["Quality"].Value).Value;
347      // remove the subscope
348      ExecutionContext.Scope.SubScopes.Remove(subScope);
349      return quality;
350    }
351
352    #region Export to dot file
353    private static void WriteDot(string path, GenealogyGraph graph) {
354      using (var file = new System.IO.StreamWriter(path)) {
355        string nl = Environment.NewLine;
356        file.WriteLine("digraph \"lineage " + graph.AverageDegree.ToString(CultureInfo.InvariantCulture) + "\" {" + nl +
357                       "ratio=auto;" + nl +
358                       "mincross=2.0");
359        file.WriteLine("\tnode [shape=circle,label=\"\",style=filled]");
360
361        foreach (var node in graph.Values) {
362          string fillColor = String.Format("#{0:x2}{1:x2}{2:x2}", node.Color.R, node.Color.G, node.Color.B);
363          if (node.IsElite)
364            fillColor = String.Format("#{0:x2}{1:x2}{2:x2}", node.Color.R, node.Color.G, 150);
365          file.WriteLine("\t\"" + node.Id + "\" [fillcolor=\"" + fillColor + "\",label=\"" + node.Label + "\"];");
366          if (node.InEdges == null)
367            continue;
368          foreach (var edge in node.InEdges) {
369            var edgeStyle = node.InEdges.Count == 1 ? "dashed" : String.Empty;
370            file.WriteLine("\t\"" + edge.Target.Id + "\" -> \"" + node.Id + "\" [arrowsize=.5, color=\"" + fillColor + "\", style=\"" + edgeStyle + "\"];");
371          }
372        }
373        foreach (var g in graph.Values.GroupBy(x => x.Generation)) {
374          var sb = new StringBuilder();
375          sb.Append("\t{rank=same;");
376          foreach (var node in g)
377            sb.Append("\"" + node.Id + "\" ");
378          sb.Append("}\n");
379          file.Write(sb.ToString());
380        }
381        file.WriteLine("}");
382      }
383    }
384    #endregion
385
386    #region Impact values (code for calculating to be moved in separate class)
387    private Dictionary<ISymbolicExpressionTreeNode, double> CalculateImpactValues(ISymbolicExpressionTree tree) {
388      var interpreter = SymbolicExpressionInterpreter;
389      var problemData = (IRegressionProblemData)SymbolicDataAnalysisEvaluator.Parameters["ProblemData"].ActualValue;
390      var dataset = problemData.Dataset;
391      var rows = problemData.TrainingIndizes;
392      string targetVariable = problemData.TargetVariable;
393      var impactValues = new Dictionary<ISymbolicExpressionTreeNode, double>();
394      var nodes = tree.Root.GetSubtree(0).GetSubtree(0).IterateNodesPostfix().ToList();
395      var originalOutput = interpreter.GetSymbolicExpressionTreeValues(tree, dataset, rows);
396      var targetValues = dataset.GetDoubleValues(targetVariable, rows);
397      OnlineCalculatorError errorState;
398      double originalR2 = OnlinePearsonsRSquaredCalculator.Calculate(targetValues, originalOutput, out errorState);
399      if (errorState != OnlineCalculatorError.None) originalR2 = 0.0;
400
401      var constantNode = ((ConstantTreeNode)new Constant().CreateTreeNode());
402      var root = new ProgramRootSymbol().CreateTreeNode(); // root node
403      var start = new StartSymbol().CreateTreeNode(); // start node
404      root.AddSubtree(start);
405      var tempTree = new SymbolicExpressionTree(root);
406
407      foreach (ISymbolicExpressionTreeNode node in nodes) {
408        var parent = node.Parent;
409        constantNode.Value = CalculateReplacementValue(tempTree, node, tree);
410        ISymbolicExpressionTreeNode replacementNode = constantNode;
411        SwitchNode(parent, node, replacementNode);
412        var newOutput = interpreter.GetSymbolicExpressionTreeValues(tree, dataset, rows);
413        double newR2 = OnlinePearsonsRSquaredCalculator.Calculate(targetValues, newOutput, out errorState);
414        if (errorState != OnlineCalculatorError.None) newR2 = 0.0;
415
416        // impact = 0 if no change
417        // impact < 0 if new solution is better
418        // impact > 0 if new solution is worse
419        impactValues[node] = originalR2 - newR2;
420        SwitchNode(parent, replacementNode, node);
421      }
422      return impactValues;
423    }
424
425    private double CalculateReplacementValue(ISymbolicExpressionTree tempTree, ISymbolicExpressionTreeNode node, ISymbolicExpressionTree sourceTree) {
426      // remove old ADFs
427      while (tempTree.Root.SubtreeCount > 1) tempTree.Root.RemoveSubtree(1);
428      // clone ADFs of source tree
429      for (int i = 1; i < sourceTree.Root.SubtreeCount; i++) {
430        tempTree.Root.AddSubtree((ISymbolicExpressionTreeNode)sourceTree.Root.GetSubtree(i).Clone());
431      }
432      var start = tempTree.Root.GetSubtree(0);
433      while (start.SubtreeCount > 0) start.RemoveSubtree(0);
434      start.AddSubtree((ISymbolicExpressionTreeNode)node.Clone());
435      var interpreter = SymbolicExpressionInterpreter;
436      var rows = SymbolicRegressionProblemData.TrainingIndizes;
437      return interpreter.GetSymbolicExpressionTreeValues(tempTree, SymbolicRegressionProblemData.Dataset, rows).Median();
438    }
439
440    private static void SwitchNode(ISymbolicExpressionTreeNode root, ISymbolicExpressionTreeNode oldBranch, ISymbolicExpressionTreeNode newBranch) {
441      for (int i = 0; i < root.SubtreeCount; i++) {
442        if (root.GetSubtree(i) == oldBranch) {
443          root.RemoveSubtree(i);
444          root.InsertSubtree(i, newBranch);
445          return;
446        }
447      }
448    }
449    #endregion
450
451    #region Allele tracking
452    private bool ContainsSubtree(ISymbolicExpressionTree tree, ISymbolicExpressionTreeNode node) {
453      return tree.IterateNodesPostfix().Where(n => n.Symbol == node.Symbol && n.GetLength() == node.GetLength())
454                                       .Where(n => n.Subtrees.Any() && node.Subtrees.Any())
455                                       .Any(n => n.Subtrees.First().Symbol == node.Subtrees.First().Symbol);
456    }
457    #endregion
458
459    #region Extra / not really needed
460    private IEnumerable<ISymbolicExpressionTreeNode> IterateNodes(ISymbolicExpressionTree tree) {
461      return IterateNodes(tree.Root);
462    }
463
464    private static IEnumerable<ISymbolicExpressionTreeNode> IterateNodes(ISymbolicExpressionTreeNode root) {
465      var list = new List<ISymbolicExpressionTreeNode> { root };
466      int offset = 0, count = 1;
467      while (offset != count) {
468        var c = count;
469        for (int i = offset; i != count; ++i) {
470          yield return list[i];
471          if (!list[i].Subtrees.Any()) continue;
472          list.AddRange(list[i].Subtrees);
473        }
474        offset = c;
475        count = list.Count;
476      }
477    }
478    #endregion
479  }
480}
Note: See TracBrowser for help on using the repository browser.