Free cookie consent management tool by TermsFeed Policy Generator

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

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

#1081: Improved performance of time series prognosis.

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