Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.TimeSeries/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Interpreter/SymbolicDataAnalysisExpressionTreeInterpreter.cs @ 7930

Last change on this file since 7930 was 7930, checked in by mkommend, 12 years ago

#1081: Refactored symbolic expression tree interpreter in preparation for autoregressive single variate prognosis.

File size: 23.5 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 HeuristicLab.Common;
25using HeuristicLab.Core;
26using HeuristicLab.Data;
27using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
28using HeuristicLab.Parameters;
29using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
30
31namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
32  [StorableClass]
33  [Item("SymbolicDataAnalysisExpressionTreeInterpreter", "Interpreter for symbolic expression trees including automatically defined functions.")]
34  public sealed class SymbolicDataAnalysisExpressionTreeInterpreter : ParameterizedNamedItem,
35    ISymbolicDataAnalysisExpressionTreeInterpreter, ISymbolicTimeSeriesPrognosisExpressionTreeInterpreter {
36    private const string CheckExpressionsWithIntervalArithmeticParameterName = "CheckExpressionsWithIntervalArithmetic";
37    private const string EvaluatedSolutionsParameterName = "EvaluatedSolutions";
38
39    public override bool CanChangeName {
40      get { return false; }
41    }
42    public override bool CanChangeDescription {
43      get { return false; }
44    }
45
46    #region parameter properties
47    public IValueParameter<BoolValue> CheckExpressionsWithIntervalArithmeticParameter {
48      get { return (IValueParameter<BoolValue>)Parameters[CheckExpressionsWithIntervalArithmeticParameterName]; }
49    }
50
51    public IValueParameter<IntValue> EvaluatedSolutionsParameter {
52      get { return (IValueParameter<IntValue>)Parameters[EvaluatedSolutionsParameterName]; }
53    }
54    #endregion
55
56    #region properties
57    public BoolValue CheckExpressionsWithIntervalArithmetic {
58      get { return CheckExpressionsWithIntervalArithmeticParameter.Value; }
59      set { CheckExpressionsWithIntervalArithmeticParameter.Value = value; }
60    }
61
62    public IntValue EvaluatedSolutions {
63      get { return EvaluatedSolutionsParameter.Value; }
64      set { EvaluatedSolutionsParameter.Value = value; }
65    }
66    #endregion
67
68    [StorableConstructor]
69    private SymbolicDataAnalysisExpressionTreeInterpreter(bool deserializing) : base(deserializing) { }
70    private SymbolicDataAnalysisExpressionTreeInterpreter(SymbolicDataAnalysisExpressionTreeInterpreter original, Cloner cloner) : base(original, cloner) { }
71    public override IDeepCloneable Clone(Cloner cloner) {
72      return new SymbolicDataAnalysisExpressionTreeInterpreter(this, cloner);
73    }
74
75    public SymbolicDataAnalysisExpressionTreeInterpreter()
76      : base("SymbolicDataAnalysisExpressionTreeInterpreter", "Interpreter for symbolic expression trees including automatically defined functions.") {
77      Parameters.Add(new ValueParameter<BoolValue>(CheckExpressionsWithIntervalArithmeticParameterName, "Switch that determines if the interpreter checks the validity of expressions with interval arithmetic before evaluating the expression.", new BoolValue(false)));
78      Parameters.Add(new ValueParameter<IntValue>(EvaluatedSolutionsParameterName, "A counter for the total number of solutions the interpreter has evaluated", new IntValue(0)));
79    }
80
81    [StorableHook(HookType.AfterDeserialization)]
82    private void AfterDeserialization() {
83      if (!Parameters.ContainsKey(EvaluatedSolutionsParameterName))
84        Parameters.Add(new ValueParameter<IntValue>(EvaluatedSolutionsParameterName, "A counter for the total number of solutions the interpreter has evaluated", new IntValue(0)));
85    }
86
87    #region IStatefulItem
88    public void InitializeState() {
89      EvaluatedSolutions.Value = 0;
90    }
91
92    public void ClearState() {
93    }
94    #endregion
95
96    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, Dataset dataset, IEnumerable<int> rows) {
97      return GetSymbolicExpressionTreeValues(tree, dataset, new string[] { "#NOTHING#" }, rows);
98    }
99
100    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, Dataset dataset, string[] targetVariables, IEnumerable<int> rows) {
101      return GetSymbolicExpressionTreeValues(tree, dataset, targetVariables, rows, 1);
102    }
103
104    // for each row for each horizon for each target variable one value
105    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, Dataset dataset, string[] targetVariables, IEnumerable<int> rows, int horizon) {
106      if (CheckExpressionsWithIntervalArithmetic.Value)
107        throw new NotSupportedException("Interval arithmetic is not yet supported in the symbolic data analysis interpreter.");
108
109      EvaluatedSolutions.Value++; // increment the evaluated solutions counter
110      var state = PrepareInterpreterState(tree, dataset, targetVariables[0]);
111
112      // produce a n-step forecast for each target variable for all rows
113      var cachedPrognosedValues = new Dictionary<string, double[]>();
114      //foreach (var targetVariable in targetVariables)
115      //  cachedPrognosedValues[targetVariable] = new double[horizon];
116      foreach (var rowEnum in rows) {
117        int row = rowEnum;
118        for (int localRow = row; localRow < row + horizon; localRow++) {
119          //int localRow = horizonRow; // create a local variable for the ref parameter
120          yield return Evaluate(dataset, ref localRow, row - 1, state, cachedPrognosedValues);
121          //cachedPrognosedValues[targetVariables[c]][horizonRow - row] = prog;
122          state.Reset();
123        }
124      }
125    }
126
127    private InterpreterState PrepareInterpreterState(ISymbolicExpressionTree tree, Dataset dataset, string targetVariable) {
128      Instruction[] code = SymbolicExpressionTreeCompiler.Compile(tree, OpCodes.MapSymbolToOpCode);
129      int necessaryArgStackSize = 0;
130      for (int i = 0; i < code.Length; i++) {
131        Instruction instr = code[i];
132        if (instr.opCode == OpCodes.Variable) {
133          var variableTreeNode = (VariableTreeNode)instr.dynamicNode;
134          instr.iArg0 = dataset.GetReadOnlyDoubleValues(variableTreeNode.VariableName);
135          code[i] = instr;
136        } else if (instr.opCode == OpCodes.LagVariable) {
137          var laggedVariableTreeNode = (LaggedVariableTreeNode)instr.dynamicNode;
138          instr.iArg0 = dataset.GetReadOnlyDoubleValues(laggedVariableTreeNode.VariableName);
139          code[i] = instr;
140        } else if (instr.opCode == OpCodes.VariableCondition) {
141          var variableConditionTreeNode = (VariableConditionTreeNode)instr.dynamicNode;
142          instr.iArg0 = dataset.GetReadOnlyDoubleValues(variableConditionTreeNode.VariableName);
143        } else if (instr.opCode == OpCodes.Call) {
144          necessaryArgStackSize += instr.nArguments + 1;
145        }
146      }
147      return new InterpreterState(code, necessaryArgStackSize);
148    }
149
150    private double Evaluate(Dataset dataset, ref int row, int lastObservedRow, InterpreterState state, Dictionary<string, double[]> cachedPrognosedValues) {
151      Instruction currentInstr = state.NextInstruction();
152      switch (currentInstr.opCode) {
153        case OpCodes.Add: {
154            double s = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
155            for (int i = 1; i < currentInstr.nArguments; i++) {
156              s += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
157            }
158            return s;
159          }
160        case OpCodes.Sub: {
161            double s = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
162            for (int i = 1; i < currentInstr.nArguments; i++) {
163              s -= Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
164            }
165            if (currentInstr.nArguments == 1) s = -s;
166            return s;
167          }
168        case OpCodes.Mul: {
169            double p = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
170            for (int i = 1; i < currentInstr.nArguments; i++) {
171              p *= Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
172            }
173            return p;
174          }
175        case OpCodes.Div: {
176            double p = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
177            for (int i = 1; i < currentInstr.nArguments; i++) {
178              p /= Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
179            }
180            if (currentInstr.nArguments == 1) p = 1.0 / p;
181            return p;
182          }
183        case OpCodes.Average: {
184            double sum = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
185            for (int i = 1; i < currentInstr.nArguments; i++) {
186              sum += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
187            }
188            return sum / currentInstr.nArguments;
189          }
190        case OpCodes.Cos: {
191            return Math.Cos(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
192          }
193        case OpCodes.Sin: {
194            return Math.Sin(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
195          }
196        case OpCodes.Tan: {
197            return Math.Tan(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
198          }
199        case OpCodes.Square: {
200            return Math.Pow(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues), 2);
201          }
202        case OpCodes.Power: {
203            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
204            double y = Math.Round(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
205            return Math.Pow(x, y);
206          }
207        case OpCodes.SquareRoot: {
208            return Math.Sqrt(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
209          }
210        case OpCodes.Root: {
211            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
212            double y = Math.Round(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
213            return Math.Pow(x, 1 / y);
214          }
215        case OpCodes.Exp: {
216            return Math.Exp(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
217          }
218        case OpCodes.Log: {
219            return Math.Log(Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues));
220          }
221        case OpCodes.Gamma: {
222            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
223            if (double.IsNaN(x)) return double.NaN;
224            else return alglib.gammafunction(x);
225          }
226        case OpCodes.Psi: {
227            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
228            if (double.IsNaN(x)) return double.NaN;
229            else if (x.IsAlmost(0.0)) return double.NaN;
230            else if ((Math.Floor(x) - x).IsAlmost(0)) return double.NaN;
231            return alglib.psi(x);
232          }
233        case OpCodes.Dawson: {
234            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
235            if (double.IsNaN(x)) return double.NaN;
236            return alglib.dawsonintegral(x);
237          }
238        case OpCodes.ExponentialIntegralEi: {
239            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
240            if (double.IsNaN(x)) return double.NaN;
241            return alglib.exponentialintegralei(x);
242          }
243        case OpCodes.SineIntegral: {
244            double si, ci;
245            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
246            if (double.IsNaN(x)) return double.NaN;
247            else {
248              alglib.sinecosineintegrals(x, out si, out ci);
249              return si;
250            }
251          }
252        case OpCodes.CosineIntegral: {
253            double si, ci;
254            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
255            if (double.IsNaN(x)) return double.NaN;
256            else {
257              alglib.sinecosineintegrals(x, out si, out ci);
258              return ci;
259            }
260          }
261        case OpCodes.HyperbolicSineIntegral: {
262            double shi, chi;
263            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
264            if (double.IsNaN(x)) return double.NaN;
265            else {
266              alglib.hyperbolicsinecosineintegrals(x, out shi, out chi);
267              return shi;
268            }
269          }
270        case OpCodes.HyperbolicCosineIntegral: {
271            double shi, chi;
272            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
273            if (double.IsNaN(x)) return double.NaN;
274            else {
275              alglib.hyperbolicsinecosineintegrals(x, out shi, out chi);
276              return chi;
277            }
278          }
279        case OpCodes.FresnelCosineIntegral: {
280            double c = 0, s = 0;
281            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
282            if (double.IsNaN(x)) return double.NaN;
283            else {
284              alglib.fresnelintegral(x, ref c, ref s);
285              return c;
286            }
287          }
288        case OpCodes.FresnelSineIntegral: {
289            double c = 0, s = 0;
290            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
291            if (double.IsNaN(x)) return double.NaN;
292            else {
293              alglib.fresnelintegral(x, ref c, ref s);
294              return s;
295            }
296          }
297        case OpCodes.AiryA: {
298            double ai, aip, bi, bip;
299            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
300            if (double.IsNaN(x)) return double.NaN;
301            else {
302              alglib.airy(x, out ai, out aip, out bi, out bip);
303              return ai;
304            }
305          }
306        case OpCodes.AiryB: {
307            double ai, aip, bi, bip;
308            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
309            if (double.IsNaN(x)) return double.NaN;
310            else {
311              alglib.airy(x, out ai, out aip, out bi, out bip);
312              return bi;
313            }
314          }
315        case OpCodes.Norm: {
316            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
317            if (double.IsNaN(x)) return double.NaN;
318            else return alglib.normaldistribution(x);
319          }
320        case OpCodes.Erf: {
321            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
322            if (double.IsNaN(x)) return double.NaN;
323            else return alglib.errorfunction(x);
324          }
325        case OpCodes.Bessel: {
326            var x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
327            if (double.IsNaN(x)) return double.NaN;
328            else return alglib.besseli0(x);
329          }
330        case OpCodes.IfThenElse: {
331            double condition = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
332            double result;
333            if (condition > 0.0) {
334              result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); state.SkipInstructions();
335            } else {
336              state.SkipInstructions(); result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
337            }
338            return result;
339          }
340        case OpCodes.AND: {
341            double result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
342            for (int i = 1; i < currentInstr.nArguments; i++) {
343              if (result > 0.0) result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
344              else {
345                state.SkipInstructions();
346              }
347            }
348            return result > 0.0 ? 1.0 : -1.0;
349          }
350        case OpCodes.OR: {
351            double result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
352            for (int i = 1; i < currentInstr.nArguments; i++) {
353              if (result <= 0.0) result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
354              else {
355                state.SkipInstructions();
356              }
357            }
358            return result > 0.0 ? 1.0 : -1.0;
359          }
360        case OpCodes.NOT: {
361            return Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues) > 0.0 ? -1.0 : 1.0;
362          }
363        case OpCodes.GT: {
364            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
365            double y = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
366            if (x > y) return 1.0;
367            else return -1.0;
368          }
369        case OpCodes.LT: {
370            double x = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
371            double y = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
372            if (x < y) return 1.0;
373            else return -1.0;
374          }
375        case OpCodes.TimeLag: {
376            var timeLagTreeNode = (LaggedTreeNode)currentInstr.dynamicNode;
377            row += timeLagTreeNode.Lag;
378            double result = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
379            row -= timeLagTreeNode.Lag;
380            return result;
381          }
382        case OpCodes.Integral: {
383            int savedPc = state.ProgramCounter;
384            var timeLagTreeNode = (LaggedTreeNode)currentInstr.dynamicNode;
385            double sum = 0.0;
386            for (int i = 0; i < Math.Abs(timeLagTreeNode.Lag); i++) {
387              row += Math.Sign(timeLagTreeNode.Lag);
388              sum += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
389              state.ProgramCounter = savedPc;
390            }
391            row -= timeLagTreeNode.Lag;
392            sum += Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
393            return sum;
394          }
395
396        //mkommend: derivate calculation taken from:
397        //http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/
398        //one sided smooth differentiatior, N = 4
399        // y' = 1/8h (f_i + 2f_i-1, -2 f_i-3 - f_i-4)
400        case OpCodes.Derivative: {
401            int savedPc = state.ProgramCounter;
402            double f_0 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); row--;
403            state.ProgramCounter = savedPc;
404            double f_1 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); row -= 2;
405            state.ProgramCounter = savedPc;
406            double f_3 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues); row--;
407            state.ProgramCounter = savedPc;
408            double f_4 = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
409            row += 4;
410
411            return (f_0 + 2 * f_1 - 2 * f_3 - f_4) / 8; // h = 1
412          }
413        case OpCodes.Call: {
414            // evaluate sub-trees
415            double[] argValues = new double[currentInstr.nArguments];
416            for (int i = 0; i < currentInstr.nArguments; i++) {
417              argValues[i] = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
418            }
419            // push on argument values on stack
420            state.CreateStackFrame(argValues);
421
422            // save the pc
423            int savedPc = state.ProgramCounter;
424            // set pc to start of function 
425            state.ProgramCounter = (ushort)currentInstr.iArg0;
426            // evaluate the function
427            double v = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
428
429            // delete the stack frame
430            state.RemoveStackFrame();
431
432            // restore the pc => evaluation will continue at point after my subtrees 
433            state.ProgramCounter = savedPc;
434            return v;
435          }
436        case OpCodes.Arg: {
437            return state.GetStackFrameValue((ushort)currentInstr.iArg0);
438          }
439        case OpCodes.Variable: {
440            if (row < 0 || row >= dataset.Rows) return double.NaN;
441            var variableTreeNode = (VariableTreeNode)currentInstr.dynamicNode;
442            if (row <= lastObservedRow || !cachedPrognosedValues.ContainsKey(variableTreeNode.VariableName)) return ((IList<double>)currentInstr.iArg0)[row] * variableTreeNode.Weight;
443            else return cachedPrognosedValues[variableTreeNode.VariableName][row - lastObservedRow - 1] * variableTreeNode.Weight;
444          }
445        case OpCodes.LagVariable: {
446            var laggedVariableTreeNode = (LaggedVariableTreeNode)currentInstr.dynamicNode;
447            int actualRow = row + laggedVariableTreeNode.Lag;
448            if (actualRow < 0 || actualRow >= dataset.Rows)
449              return double.NaN;
450            if (actualRow <= lastObservedRow || !cachedPrognosedValues.ContainsKey(laggedVariableTreeNode.VariableName)) return ((IList<double>)currentInstr.iArg0)[actualRow] * laggedVariableTreeNode.Weight;
451            else return cachedPrognosedValues[laggedVariableTreeNode.VariableName][actualRow - lastObservedRow - 1] * laggedVariableTreeNode.Weight;
452          }
453        case OpCodes.Constant: {
454            var constTreeNode = (ConstantTreeNode)currentInstr.dynamicNode;
455            return constTreeNode.Value;
456          }
457
458        //mkommend: this symbol uses the logistic function f(x) = 1 / (1 + e^(-alpha * x) )
459        //to determine the relative amounts of the true and false branch see http://en.wikipedia.org/wiki/Logistic_function
460        case OpCodes.VariableCondition: {
461            if (row < 0 || row >= dataset.Rows)
462              return double.NaN;
463            var variableConditionTreeNode = (VariableConditionTreeNode)currentInstr.dynamicNode;
464            double variableValue;
465            if (row <= lastObservedRow || !cachedPrognosedValues.ContainsKey(variableConditionTreeNode.VariableName))
466              variableValue = ((IList<double>)currentInstr.iArg0)[row];
467            else
468              variableValue = cachedPrognosedValues[variableConditionTreeNode.VariableName][row - lastObservedRow - 1];
469
470            double x = variableValue - variableConditionTreeNode.Threshold;
471            double p = 1 / (1 + Math.Exp(-variableConditionTreeNode.Slope * x));
472
473            double trueBranch = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
474            double falseBranch = Evaluate(dataset, ref row, lastObservedRow, state, cachedPrognosedValues);
475
476            return trueBranch * p + falseBranch * (1 - p);
477          }
478        default: throw new NotSupportedException();
479      }
480    }
481  }
482}
Note: See TracBrowser for help on using the repository browser.