source: stable/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Interpreter/SymbolicDataAnalysisExpressionTreeBatchInterpreter.cs @ 17170

Last change on this file since 17170 was 17170, checked in by gkronber, 2 months ago

#2958: merged r16301 from trunk to stable

File size: 9.2 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5using HeuristicLab.Common;
6using HeuristicLab.Core;
7using HeuristicLab.Data;
8using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
9using HeuristicLab.Parameters;
10using HEAL.Attic;
11
12using static HeuristicLab.Problems.DataAnalysis.Symbolic.BatchOperations;
13
14namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
15  [Item("SymbolicDataAnalysisExpressionTreeBatchInterpreter", "An interpreter that uses batching and vectorization techniques to achieve faster performance.")]
16  [StorableType("BEB15146-BB95-4838-83AC-6838543F017B")]
17  public class SymbolicDataAnalysisExpressionTreeBatchInterpreter : ParameterizedNamedItem, ISymbolicDataAnalysisExpressionTreeInterpreter {
18    private const string EvaluatedSolutionsParameterName = "EvaluatedSolutions";
19
20    #region parameters
21    public IFixedValueParameter<IntValue> EvaluatedSolutionsParameter {
22      get { return (IFixedValueParameter<IntValue>)Parameters[EvaluatedSolutionsParameterName]; }
23    }
24    #endregion
25
26    #region properties
27    public int EvaluatedSolutions {
28      get { return EvaluatedSolutionsParameter.Value.Value; }
29      set { EvaluatedSolutionsParameter.Value.Value = value; }
30    }
31    #endregion
32
33    public void ClearState() { }
34
35    public SymbolicDataAnalysisExpressionTreeBatchInterpreter() {
36      Parameters.Add(new FixedValueParameter<IntValue>(EvaluatedSolutionsParameterName, "A counter for the total number of solutions the interpreter has evaluated", new IntValue(0)));
37    }
38
39    [StorableConstructor]
40    protected SymbolicDataAnalysisExpressionTreeBatchInterpreter(StorableConstructorFlag _) : base(_) { }
41    protected SymbolicDataAnalysisExpressionTreeBatchInterpreter(SymbolicDataAnalysisExpressionTreeBatchInterpreter original, Cloner cloner) : base(original, cloner) {
42    }
43    public override IDeepCloneable Clone(Cloner cloner) {
44      return new SymbolicDataAnalysisExpressionTreeBatchInterpreter(this, cloner);
45    }
46
47    private void LoadData(BatchInstruction instr, int[] rows, int rowIndex, int batchSize) {
48      for (int i = 0; i < batchSize; ++i) {
49        var row = rows[rowIndex] + i;
50        instr.buf[i] = instr.weight * instr.data[row];
51      }
52    }
53
54    private void Evaluate(BatchInstruction[] code, int[] rows, int rowIndex, int batchSize) {
55      for (int i = code.Length - 1; i >= 0; --i) {
56        var instr = code[i];
57        var c = instr.childIndex;
58        var n = instr.narg;
59
60        switch (instr.opcode) {
61          case OpCodes.Variable: {
62              LoadData(instr, rows, rowIndex, batchSize);
63              break;
64            }
65          case OpCodes.Constant: break; // nothing to do here, don't remove because we want to prevent falling into the default case here.
66          case OpCodes.Add: {
67              Load(instr.buf, code[c].buf);
68              for (int j = 1; j < n; ++j) {
69                Add(instr.buf, code[c + j].buf);
70              }
71              break;
72            }
73
74          case OpCodes.Sub: {
75              if (n == 1) {
76                Neg(instr.buf, code[c].buf);
77              } else {
78                Load(instr.buf, code[c].buf);
79                for (int j = 1; j < n; ++j) {
80                  Sub(instr.buf, code[c + j].buf);
81                }
82              }
83              break;
84            }
85
86          case OpCodes.Mul: {
87              Load(instr.buf, code[c].buf);
88              for (int j = 1; j < n; ++j) {
89                Mul(instr.buf, code[c + j].buf);
90              }
91              break;
92            }
93
94          case OpCodes.Div: {
95              if (n == 1) {
96                Inv(instr.buf, code[c].buf);
97              } else {
98                Load(instr.buf, code[c].buf);
99                for (int j = 1; j < n; ++j) {
100                  Div(instr.buf, code[c + j].buf);
101                }
102              }
103              break;
104            }
105
106          case OpCodes.Square: {
107              Square(instr.buf, code[c].buf);
108              break;
109            }
110
111          case OpCodes.Root: {
112              Load(instr.buf, code[c].buf);
113              Root(instr.buf, code[c + 1].buf);
114              break;
115            }
116
117          case OpCodes.SquareRoot: {
118              Sqrt(instr.buf, code[c].buf);
119              break;
120            }
121
122          case OpCodes.Cube: {
123              Cube(instr.buf, code[c].buf);
124              break;
125            }
126          case OpCodes.CubeRoot: {
127              CubeRoot(instr.buf, code[c].buf);
128              break;
129            }
130
131          case OpCodes.Power: {
132              Load(instr.buf, code[c].buf);
133              Pow(instr.buf, code[c + 1].buf);
134              break;
135            }
136
137          case OpCodes.Exp: {
138              Exp(instr.buf, code[c].buf);
139              break;
140            }
141
142          case OpCodes.Log: {
143              Log(instr.buf, code[c].buf);
144              break;
145            }
146
147          case OpCodes.Sin: {
148              Sin(instr.buf, code[c].buf);
149              break;
150            }
151
152          case OpCodes.Cos: {
153              Cos(instr.buf, code[c].buf);
154              break;
155            }
156
157          case OpCodes.Tan: {
158              Tan(instr.buf, code[c].buf);
159              break;
160            }
161          case OpCodes.Tanh: {
162              Tanh(instr.buf, code[c].buf);
163              break;
164            }
165          case OpCodes.Absolute: {
166              Absolute(instr.buf, code[c].buf);
167              break;
168            }
169
170          case OpCodes.AnalyticQuotient: {
171              Load(instr.buf, code[c].buf);
172              AnalyticQuotient(instr.buf, code[c + 1].buf);
173              break;
174            }
175          default: throw new NotSupportedException($"This interpreter does not support {(OpCode)instr.opcode}");
176        }
177      }
178    }
179
180    private readonly object syncRoot = new object();
181
182    [ThreadStatic]
183    private Dictionary<string, double[]> cachedData;
184
185    [ThreadStatic]
186    private IDataset dataset;
187
188    private void InitCache(IDataset dataset) {
189      this.dataset = dataset;
190      cachedData = new Dictionary<string, double[]>();
191      foreach (var v in dataset.DoubleVariables) {
192        cachedData[v] = dataset.GetDoubleValues(v).ToArray();
193      }
194    }
195
196    public void InitializeState() {
197      cachedData = null;
198      dataset = null;
199      EvaluatedSolutions = 0;
200    }
201
202    private double[] GetValues(ISymbolicExpressionTree tree, IDataset dataset, int[] rows) {
203      if (cachedData == null || this.dataset != dataset) {
204        InitCache(dataset);
205      }
206
207      var code = Compile(tree, dataset, OpCodes.MapSymbolToOpCode);
208      var remainingRows = rows.Length % BATCHSIZE;
209      var roundedTotal = rows.Length - remainingRows;
210
211      var result = new double[rows.Length];
212
213      for (int rowIndex = 0; rowIndex < roundedTotal; rowIndex += BATCHSIZE) {
214        Evaluate(code, rows, rowIndex, BATCHSIZE);
215        Array.Copy(code[0].buf, 0, result, rowIndex, BATCHSIZE);
216      }
217
218      if (remainingRows > 0) {
219        Evaluate(code, rows, roundedTotal, remainingRows);
220        Array.Copy(code[0].buf, 0, result, roundedTotal, remainingRows);
221      }
222
223      // when evaluation took place without any error, we can increment the counter
224      lock (syncRoot) {
225        EvaluatedSolutions++;
226      }
227
228      return result;
229    }
230
231    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, IDataset dataset, int[] rows) {
232      return GetValues(tree, dataset, rows);
233    }
234
235    public IEnumerable<double> GetSymbolicExpressionTreeValues(ISymbolicExpressionTree tree, IDataset dataset, IEnumerable<int> rows) {
236      return GetSymbolicExpressionTreeValues(tree, dataset, rows.ToArray());
237    }
238
239    private BatchInstruction[] Compile(ISymbolicExpressionTree tree, IDataset dataset, Func<ISymbolicExpressionTreeNode, byte> opCodeMapper) {
240      var root = tree.Root.GetSubtree(0).GetSubtree(0);
241      var code = new BatchInstruction[root.GetLength()];
242      if (root.SubtreeCount > ushort.MaxValue) throw new ArgumentException("Number of subtrees is too big (>65.535)");
243      int c = 1, i = 0;
244      foreach (var node in root.IterateNodesBreadth()) {
245        if (node.SubtreeCount > ushort.MaxValue) throw new ArgumentException("Number of subtrees is too big (>65.535)");
246        code[i] = new BatchInstruction {
247          opcode = opCodeMapper(node),
248          narg = (ushort)node.SubtreeCount,
249          buf = new double[BATCHSIZE],
250          childIndex = c
251        };
252        if (node is VariableTreeNode variable) {
253          code[i].weight = variable.Weight;
254          if (cachedData.ContainsKey(variable.VariableName)) {
255            code[i].data = cachedData[variable.VariableName];
256          } else {
257            code[i].data = dataset.GetReadOnlyDoubleValues(variable.VariableName).ToArray();
258            cachedData[variable.VariableName] = code[i].data;
259          }
260        } else if (node is ConstantTreeNode constant) {
261          code[i].value = constant.Value;
262          for (int j = 0; j < BATCHSIZE; ++j)
263            code[i].buf[j] = code[i].value;
264        }
265        c += node.SubtreeCount;
266        ++i;
267      }
268      return code;
269    }
270  }
271}
Note: See TracBrowser for help on using the repository browser.