Free cookie consent management tool by TermsFeed Policy Generator

source: branches/DataAnalysis/HeuristicLab.Problems.DataAnalysis.Regression/3.3/Symbolic/Analyzers/SymbolicRegressionTournamentPruning.cs @ 6627

Last change on this file since 6627 was 5275, checked in by gkronber, 14 years ago

Merged changes from trunk to data analysis exploration branch and added fractional distance metric evaluator. #1142

File size: 23.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2010 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.Collections.Generic;
23using System.Linq;
24using HeuristicLab.Core;
25using HeuristicLab.Data;
26using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
27using HeuristicLab.Operators;
28using HeuristicLab.Optimization;
29using HeuristicLab.Parameters;
30using HeuristicLab.Problems.DataAnalysis.Symbolic;
31using HeuristicLab.Problems.DataAnalysis.Symbolic.Symbols;
32using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
33using System;
34using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding.Symbols;
35using HeuristicLab.Common;
36
37namespace HeuristicLab.Problems.DataAnalysis.Regression.Symbolic.Analyzers {
38  public class SymbolicRegressionTournamentPruning : SingleSuccessorOperator, ISymbolicRegressionAnalyzer {
39    private const string RandomParameterName = "Random";
40    private const string SymbolicExpressionTreeParameterName = "SymbolicExpressionTree";
41    private const string DataAnalysisProblemDataParameterName = "DataAnalysisProblemData";
42    private const string SamplesStartParameterName = "SamplesStart";
43    private const string SamplesEndParameterName = "SamplesEnd";
44    private const string EvaluatorParameterName = "Evaluator";
45    private const string MaximizationParameterName = "Maximization";
46    private const string SymbolicExpressionTreeInterpreterParameterName = "SymbolicExpressionTreeInterpreter";
47    private const string UpperEstimationLimitParameterName = "UpperEstimationLimit";
48    private const string LowerEstimationLimitParameterName = "LowerEstimationLimit";
49    private const string MaxPruningRatioParameterName = "MaxPruningRatio";
50    private const string TournamentSizeParameterName = "TournamentSize";
51    private const string PopulationPercentileStartParameterName = "PopulationPercentileStart";
52    private const string PopulationPercentileEndParameterName = "PopulationPercentileEnd";
53    private const string QualityGainWeightParameterName = "QualityGainWeight";
54    private const string IterationsParameterName = "Iterations";
55    private const string FirstPruningGenerationParameterName = "FirstPruningGeneration";
56    private const string PruningFrequencyParameterName = "PruningFrequency";
57    private const string GenerationParameterName = "Generations";
58    private const string ResultsParameterName = "Results";
59
60    #region parameter properties
61    public ILookupParameter<IRandom> RandomParameter {
62      get { return (ILookupParameter<IRandom>)Parameters[RandomParameterName]; }
63    }
64    public ScopeTreeLookupParameter<SymbolicExpressionTree> SymbolicExpressionTreeParameter {
65      get { return (ScopeTreeLookupParameter<SymbolicExpressionTree>)Parameters[SymbolicExpressionTreeParameterName]; }
66    }
67    public ScopeTreeLookupParameter<DoubleValue> QualityParameter {
68      get { return (ScopeTreeLookupParameter<DoubleValue>)Parameters["Quality"]; }
69    }
70    public ILookupParameter<DataAnalysisProblemData> DataAnalysisProblemDataParameter {
71      get { return (ILookupParameter<DataAnalysisProblemData>)Parameters[DataAnalysisProblemDataParameterName]; }
72    }
73    public ILookupParameter<ISymbolicExpressionTreeInterpreter> SymbolicExpressionTreeInterpreterParameter {
74      get { return (ILookupParameter<ISymbolicExpressionTreeInterpreter>)Parameters[SymbolicExpressionTreeInterpreterParameterName]; }
75    }
76    public IValueLookupParameter<DoubleValue> UpperEstimationLimitParameter {
77      get { return (IValueLookupParameter<DoubleValue>)Parameters[UpperEstimationLimitParameterName]; }
78    }
79    public IValueLookupParameter<DoubleValue> LowerEstimationLimitParameter {
80      get { return (IValueLookupParameter<DoubleValue>)Parameters[LowerEstimationLimitParameterName]; }
81    }
82    public IValueLookupParameter<IntValue> SamplesStartParameter {
83      get { return (IValueLookupParameter<IntValue>)Parameters[SamplesStartParameterName]; }
84    }
85    public IValueLookupParameter<IntValue> SamplesEndParameter {
86      get { return (IValueLookupParameter<IntValue>)Parameters[SamplesEndParameterName]; }
87    }
88    public IValueLookupParameter<PercentValue> RelativeNumberOfEvaluatedRowsParameters {
89      get { return (IValueLookupParameter<PercentValue>)Parameters["RelativeNumberOfEvaluatedRows"]; }
90    }
91    public ILookupParameter<ISymbolicRegressionEvaluator> EvaluatorParameter {
92      get { return (ILookupParameter<ISymbolicRegressionEvaluator>)Parameters[EvaluatorParameterName]; }
93    }
94    public ILookupParameter<BoolValue> MaximizationParameter {
95      get { return (ILookupParameter<BoolValue>)Parameters[MaximizationParameterName]; }
96    }
97    public IValueLookupParameter<DoubleValue> MaxPruningRatioParameter {
98      get { return (IValueLookupParameter<DoubleValue>)Parameters[MaxPruningRatioParameterName]; }
99    }
100    public IValueLookupParameter<IntValue> TournamentSizeParameter {
101      get { return (IValueLookupParameter<IntValue>)Parameters[TournamentSizeParameterName]; }
102    }
103    public IValueLookupParameter<DoubleValue> PopulationPercentileStartParameter {
104      get { return (IValueLookupParameter<DoubleValue>)Parameters[PopulationPercentileStartParameterName]; }
105    }
106    public IValueLookupParameter<DoubleValue> PopulationPercentileEndParameter {
107      get { return (IValueLookupParameter<DoubleValue>)Parameters[PopulationPercentileEndParameterName]; }
108    }
109    public IValueLookupParameter<DoubleValue> QualityGainWeightParameter {
110      get { return (IValueLookupParameter<DoubleValue>)Parameters[QualityGainWeightParameterName]; }
111    }
112    public IValueLookupParameter<IntValue> IterationsParameter {
113      get { return (IValueLookupParameter<IntValue>)Parameters[IterationsParameterName]; }
114    }
115    public IValueLookupParameter<IntValue> FirstPruningGenerationParameter {
116      get { return (IValueLookupParameter<IntValue>)Parameters[FirstPruningGenerationParameterName]; }
117    }
118    public IValueLookupParameter<IntValue> PruningFrequencyParameter {
119      get { return (IValueLookupParameter<IntValue>)Parameters[PruningFrequencyParameterName]; }
120    }
121    public ILookupParameter<IntValue> GenerationParameter {
122      get { return (ILookupParameter<IntValue>)Parameters[GenerationParameterName]; }
123    }
124    public ILookupParameter<ResultCollection> ResultsParameter {
125      get { return (ILookupParameter<ResultCollection>)Parameters[ResultsParameterName]; }
126    }
127    public IValueLookupParameter<BoolValue> ApplyPruningParameter {
128      get { return (IValueLookupParameter<BoolValue>)Parameters["ApplyPruning"]; }
129    }
130    public IValueLookupParameter<IntValue> MinimalTreeSizeParameter {
131      get { return (IValueLookupParameter<IntValue>)Parameters["MinimalTreeSize"]; }
132    }
133    #endregion
134    #region properties
135    public IRandom Random {
136      get { return RandomParameter.ActualValue; }
137    }
138    public ItemArray<SymbolicExpressionTree> SymbolicExpressionTree {
139      get { return SymbolicExpressionTreeParameter.ActualValue; }
140    }
141    public DataAnalysisProblemData DataAnalysisProblemData {
142      get { return DataAnalysisProblemDataParameter.ActualValue; }
143    }
144    public ISymbolicExpressionTreeInterpreter SymbolicExpressionTreeInterpreter {
145      get { return SymbolicExpressionTreeInterpreterParameter.ActualValue; }
146    }
147    public DoubleValue UpperEstimationLimit {
148      get { return UpperEstimationLimitParameter.ActualValue; }
149    }
150    public DoubleValue LowerEstimationLimit {
151      get { return LowerEstimationLimitParameter.ActualValue; }
152    }
153    public IntValue SamplesStart {
154      get { return SamplesStartParameter.ActualValue; }
155    }
156    public IntValue SamplesEnd {
157      get { return SamplesEndParameter.ActualValue; }
158    }
159    public ISymbolicRegressionEvaluator Evaluator {
160      get { return EvaluatorParameter.ActualValue; }
161    }
162    public BoolValue Maximization {
163      get { return MaximizationParameter.ActualValue; }
164    }
165    public DoubleValue MaxPruningRatio {
166      get { return MaxPruningRatioParameter.ActualValue; }
167    }
168    public IntValue TournamentSize {
169      get { return TournamentSizeParameter.ActualValue; }
170    }
171    public DoubleValue PopulationPercentileStart {
172      get { return PopulationPercentileStartParameter.ActualValue; }
173    }
174    public DoubleValue PopulationPercentileEnd {
175      get { return PopulationPercentileEndParameter.ActualValue; }
176    }
177    public DoubleValue QualityGainWeight {
178      get { return QualityGainWeightParameter.ActualValue; }
179    }
180    public IntValue Iterations {
181      get { return IterationsParameter.ActualValue; }
182    }
183    public IntValue PruningFrequency {
184      get { return PruningFrequencyParameter.ActualValue; }
185    }
186    public IntValue FirstPruningGeneration {
187      get { return FirstPruningGenerationParameter.ActualValue; }
188    }
189    public IntValue Generation {
190      get { return GenerationParameter.ActualValue; }
191    }
192    #endregion
193    [StorableConstructor]
194    protected SymbolicRegressionTournamentPruning(bool deserializing) : base(deserializing) { }
195    protected SymbolicRegressionTournamentPruning(SymbolicRegressionTournamentPruning original, Cloner cloner)
196      : base(original, cloner) {
197    }
198    public SymbolicRegressionTournamentPruning()
199      : base() {
200      Parameters.Add(new LookupParameter<IRandom>(RandomParameterName, "A random number generator."));
201      Parameters.Add(new ScopeTreeLookupParameter<SymbolicExpressionTree>(SymbolicExpressionTreeParameterName, "The symbolic expression trees to prune."));
202      Parameters.Add(new ScopeTreeLookupParameter<DoubleValue>("Quality"));
203      Parameters.Add(new LookupParameter<DataAnalysisProblemData>(DataAnalysisProblemDataParameterName, "The data analysis problem data to use for branch impact evaluation."));
204      Parameters.Add(new LookupParameter<ISymbolicExpressionTreeInterpreter>(SymbolicExpressionTreeInterpreterParameterName, "The interpreter to use for node impact evaluation"));
205      Parameters.Add(new ValueLookupParameter<IntValue>(SamplesStartParameterName, "The first row index of the dataset partition to use for branch impact evaluation."));
206      Parameters.Add(new ValueLookupParameter<IntValue>(SamplesEndParameterName, "The last row index of the dataset partition to use for branch impact evaluation."));
207      Parameters.Add(new LookupParameter<ISymbolicRegressionEvaluator>(EvaluatorParameterName, "The evaluator that should be used to determine which branches are not relevant."));
208      Parameters.Add(new LookupParameter<BoolValue>(MaximizationParameterName, "The direction of optimization."));
209      Parameters.Add(new ValueLookupParameter<BoolValue>("ApplyPruning"));
210      Parameters.Add(new ValueLookupParameter<DoubleValue>(MaxPruningRatioParameterName, "The maximal relative size of the pruned branch.", new DoubleValue(0.5)));
211      Parameters.Add(new ValueLookupParameter<IntValue>(TournamentSizeParameterName, "The number of branches to compare for pruning", new IntValue(10)));
212      Parameters.Add(new ValueLookupParameter<DoubleValue>(PopulationPercentileStartParameterName, "The start of the population percentile to consider for pruning.", new DoubleValue(0.25)));
213      Parameters.Add(new ValueLookupParameter<DoubleValue>(PopulationPercentileEndParameterName, "The end of the population percentile to consider for pruning.", new DoubleValue(0.75)));
214      Parameters.Add(new ValueLookupParameter<DoubleValue>(QualityGainWeightParameterName, "The weight of the quality gain relative to the size gain.", new DoubleValue(1.0)));
215      Parameters.Add(new ValueLookupParameter<DoubleValue>(UpperEstimationLimitParameterName, "The upper estimation limit to use for evaluation."));
216      Parameters.Add(new ValueLookupParameter<DoubleValue>(LowerEstimationLimitParameterName, "The lower estimation limit to use for evaluation."));
217      Parameters.Add(new ValueLookupParameter<IntValue>(IterationsParameterName, "The number of pruning iterations to apply for each tree.", new IntValue(1)));
218      Parameters.Add(new ValueLookupParameter<IntValue>(FirstPruningGenerationParameterName, "The first generation when pruning should be applied.", new IntValue(1)));
219      Parameters.Add(new ValueLookupParameter<IntValue>(PruningFrequencyParameterName, "The frequency of pruning operations (1: every generation, 2: every second generation...)", new IntValue(1)));
220      Parameters.Add(new LookupParameter<IntValue>(GenerationParameterName, "The current generation."));
221      Parameters.Add(new LookupParameter<ResultCollection>(ResultsParameterName, "The results collection."));
222      Parameters.Add(new ValueLookupParameter<PercentValue>("RelativeNumberOfEvaluatedRows", new PercentValue(1.0)));
223      Parameters.Add(new ValueLookupParameter<IntValue>("MinimalTreeSize", new IntValue(15)));
224    }
225
226    public override IDeepCloneable Clone(Cloner cloner) {
227      return new SymbolicRegressionTournamentPruning(this, cloner);
228    }
229
230    [StorableHook(HookType.AfterDeserialization)]
231    private void AfterDeserialization() {
232      #region compatibility remove before releasing 3.3.1
233      if (!Parameters.ContainsKey(EvaluatorParameterName)) {
234        Parameters.Add(new LookupParameter<ISymbolicRegressionEvaluator>(EvaluatorParameterName, "The evaluator which should be used to evaluate the solution on the validation set."));
235      }
236      if (!Parameters.ContainsKey(MaximizationParameterName)) {
237        Parameters.Add(new LookupParameter<BoolValue>(MaximizationParameterName, "The direction of optimization."));
238      }
239      if (!Parameters.ContainsKey("ApplyPruning")) {
240        Parameters.Add(new ValueLookupParameter<BoolValue>("ApplyPruning"));
241      }
242      if (!Parameters.ContainsKey("Quality")) {
243        Parameters.Add(new ScopeTreeLookupParameter<DoubleValue>("Quality"));
244      }
245      if (!Parameters.ContainsKey("RelativeNumberOfEvaluatedRows")) {
246        Parameters.Add(new ValueLookupParameter<PercentValue>("RelativeNumberOfEvaluatedRows", new PercentValue(1.0)));
247      }
248      if (!Parameters.ContainsKey("MinimalTreeSize")) {
249        Parameters.Add(new ValueLookupParameter<IntValue>("MinimalTreeSize", new IntValue(15)));
250      }
251
252      #endregion
253    }
254
255    public override IOperation Apply() {
256      bool pruningCondition =
257        (ApplyPruningParameter.ActualValue.Value) &&
258        (Generation.Value >= FirstPruningGeneration.Value) &&
259        ((Generation.Value - FirstPruningGeneration.Value) % PruningFrequency.Value == 0);
260      if (pruningCondition) {
261        int n = SymbolicExpressionTree.Length;
262        double percentileStart = PopulationPercentileStart.Value;
263        double percentileEnd = PopulationPercentileEnd.Value;
264        // for each tree in the given percentile
265        ItemArray<SymbolicExpressionTree> trees = SymbolicExpressionTree;
266        ItemArray<DoubleValue> quality = QualityParameter.ActualValue;
267        bool maximization = Maximization.Value;
268        var selectedTrees = (from index in Enumerable.Range(0, n)
269                             orderby maximization ? -quality[index].Value : quality[index].Value
270                             select new { Tree = trees[index], Quality = quality[index] })
271                                                            .Skip((int)(n * percentileStart))
272                                                            .Take((int)(n * (percentileEnd - percentileStart)));
273        foreach (var pair in selectedTrees) {
274          Prune(Random, pair.Tree, pair.Quality, Iterations.Value, TournamentSize.Value,
275            DataAnalysisProblemData, SamplesStart.Value, SamplesEnd.Value, RelativeNumberOfEvaluatedRowsParameters.ActualValue.Value,
276            SymbolicExpressionTreeInterpreter, Evaluator, Maximization.Value,
277            LowerEstimationLimit.Value, UpperEstimationLimit.Value,
278            MinimalTreeSizeParameter.ActualValue.Value, MaxPruningRatio.Value, QualityGainWeight.Value);
279        }
280      }
281      return base.Apply();
282    }
283
284    public static void Prune(IRandom random, SymbolicExpressionTree tree, DoubleValue quality, int iterations, int tournamentSize,
285      DataAnalysisProblemData problemData, int samplesStart, int samplesEnd, double relativeNumberOfEvaluatedRows,
286      ISymbolicExpressionTreeInterpreter interpreter, ISymbolicRegressionEvaluator evaluator, bool maximization,
287      double lowerEstimationLimit, double upperEstimationLimit,
288      int minTreeSize, double maxPruningRatio, double qualityGainWeight) {
289
290     
291      int originalSize = tree.Size;
292      // min size of the resulting pruned tree
293      int minPrunedSize = (int)(originalSize * (1 - maxPruningRatio));
294      minPrunedSize = Math.Max(minPrunedSize, minTreeSize);
295
296      // use the same subset of rows for all iterations and for all pruning tournaments
297      IEnumerable<int> rows = RandomEnumerable.SampleRandomNumbers(samplesStart, samplesEnd, (int)Math.Ceiling((samplesEnd - samplesStart) * relativeNumberOfEvaluatedRows));
298      SymbolicExpressionTree prunedTree = tree;
299      for (int iteration = 0; iteration < iterations; iteration++) {
300        // maximally prune a branch such that the resulting tree size is not smaller than (1-maxPruningRatio) of the original tree
301        int maxPrunedBranchSize = tree.Size - minPrunedSize;
302        if (maxPrunedBranchSize > 0) {
303          PruneTournament(prunedTree, quality, random, tournamentSize, maxPrunedBranchSize, maximization, qualityGainWeight, evaluator, interpreter, problemData.Dataset, problemData.TargetVariable.Value, rows, lowerEstimationLimit, upperEstimationLimit);
304        }
305      }
306    }
307
308    private class PruningPoint {
309      public SymbolicExpressionTreeNode Parent { get; private set; }
310      public SymbolicExpressionTreeNode Branch { get; private set; }
311      public int SubTreeIndex { get; private set; }
312      public PruningPoint(SymbolicExpressionTreeNode parent, SymbolicExpressionTreeNode branch, int index) {
313        Parent = parent;
314        Branch = branch;
315        SubTreeIndex = index;
316      }
317    }
318
319    private static void PruneTournament(SymbolicExpressionTree tree, DoubleValue quality, IRandom random, int tournamentSize,
320      int maxPrunedBranchSize, bool maximization, double qualityGainWeight, ISymbolicRegressionEvaluator evaluator, ISymbolicExpressionTreeInterpreter interpreter,
321      Dataset ds, string targetVariable, IEnumerable<int> rows, double lowerEstimationLimit, double upperEstimationLimit) {
322      // make a clone for pruningEvaluation
323      SymbolicExpressionTree pruningEvaluationTree = (SymbolicExpressionTree)tree.Clone();
324      var prunePoints = (from node in pruningEvaluationTree.Root.SubTrees[0].IterateNodesPostfix()
325                         from subTree in node.SubTrees
326                         let subTreeSize = subTree.GetSize()
327                         where subTreeSize <= maxPrunedBranchSize
328                         where !(subTree.Symbol is Constant)
329                         select new PruningPoint(node, subTree, node.SubTrees.IndexOf(subTree)))
330         .ToList();
331      double originalQuality = quality.Value;
332      double originalSize = tree.Size;
333      if (prunePoints.Count > 0) {
334        double bestCoeff = double.PositiveInfinity;
335        List<PruningPoint> tournamentGroup;
336        if (prunePoints.Count > tournamentSize) {
337          tournamentGroup = new List<PruningPoint>();
338          for (int i = 0; i < tournamentSize; i++) {
339            tournamentGroup.Add(prunePoints.SelectRandom(random));
340          }
341        } else {
342          tournamentGroup = prunePoints;
343        }
344        foreach (PruningPoint prunePoint in tournamentGroup) {
345          double replacementValue = CalculateReplacementValue(prunePoint.Branch, interpreter, ds, rows);
346
347          // temporarily replace the branch with a constant
348          prunePoint.Parent.RemoveSubTree(prunePoint.SubTreeIndex);
349          var constNode = CreateConstant(replacementValue);
350          prunePoint.Parent.InsertSubTree(prunePoint.SubTreeIndex, constNode);
351
352          // evaluate the pruned tree
353          double prunedQuality = evaluator.Evaluate(interpreter, pruningEvaluationTree,
354  lowerEstimationLimit, upperEstimationLimit, ds, targetVariable, rows);
355
356          double prunedSize = originalSize - prunePoint.Branch.GetSize() + 1;
357
358          double coeff = CalculatePruningCoefficient(maximization, qualityGainWeight, originalQuality, originalSize, prunedQuality, prunedSize);
359          if (coeff < bestCoeff) {
360            bestCoeff = coeff;
361            // clone the currently pruned tree
362            SymbolicExpressionTree bestTree = (SymbolicExpressionTree)pruningEvaluationTree.Clone();
363
364            // and update original tree and quality
365            tree.Root = bestTree.Root;
366            quality.Value = prunedQuality;
367          }
368
369          // restore tree that is used for pruning evaluation
370          prunePoint.Parent.RemoveSubTree(prunePoint.SubTreeIndex);
371          prunePoint.Parent.InsertSubTree(prunePoint.SubTreeIndex, prunePoint.Branch);
372        }
373      }
374    }
375
376    private static double CalculatePruningCoefficient(bool maximization, double qualityGainWeight, double originalQuality, double originalSize, double prunedQuality, double prunedSize) {
377      // deteriation in quality:
378      // exp: MSE : newMse < origMse (improvement) => prefer the larger improvement
379      //      MSE : newMse > origMse (deteriation) => prefer the smaller deteriation
380      //      MSE : minimize: newMse / origMse
381      //      R²  : newR² > origR²   (improvment) => prefer the larger improvment
382      //      R²  : newR² < origR²   (deteriation) => prefer smaller deteriation
383      //      R²  : minimize: origR² / newR²
384      double qualityDeteriation = maximization ? originalQuality / prunedQuality : prunedQuality / originalQuality;
385      // size of the pruned tree is always smaller than the size of the original tree
386      // same change in quality => prefer pruning operation that removes a larger tree
387      return (qualityDeteriation * qualityGainWeight) / (originalSize / prunedSize);
388    }
389
390    private static double CalculateReplacementValue(SymbolicExpressionTreeNode branch, ISymbolicExpressionTreeInterpreter interpreter, Dataset ds, IEnumerable<int> rows) {
391      SymbolicExpressionTreeNode start = (new StartSymbol()).CreateTreeNode();
392      start.AddSubTree(branch);
393      SymbolicExpressionTreeNode root = (new ProgramRootSymbol()).CreateTreeNode();
394      root.AddSubTree(start);
395      SymbolicExpressionTree tree = new SymbolicExpressionTree(root);
396      IEnumerable<double> branchValues = interpreter.GetSymbolicExpressionTreeValues(tree, ds, rows);
397      return branchValues.Average();
398    }
399
400    private static SymbolicExpressionTreeNode CreateConstant(double constantValue) {
401      var node = (ConstantTreeNode)(new Constant()).CreateTreeNode();
402      node.Value = constantValue;
403      return node;
404    }
405  }
406}
Note: See TracBrowser for help on using the repository browser.