Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2925_AutoDiffForDynamicalModels/HeuristicLab.Problems.DynamicalSystemsModelling/3.3/Problem.cs @ 16954

Last change on this file since 16954 was 16954, checked in by gkronber, 5 years ago

#2925: Add problem instance provider and instances. Use penalized regression splines for calculation of numeric differences (for pre-tuning).

File size: 97.2 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2018 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.Diagnostics;
25using System.Globalization;
26using System.Linq;
27using HeuristicLab.Analysis;
28using HeuristicLab.Collections;
29using HeuristicLab.Common;
30using HeuristicLab.Core;
31using HeuristicLab.Data;
32using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
33using HeuristicLab.Optimization;
34using HeuristicLab.Parameters;
35using HeuristicLab.Problems.DataAnalysis;
36using HeuristicLab.Problems.DataAnalysis.Symbolic;
37using HeuristicLab.Problems.Instances;
38using Variable = HeuristicLab.Problems.DataAnalysis.Symbolic.Variable;
39using HEAL.Attic;
40using HeuristicLab.Problems.DataAnalysis.Symbolic.Regression;
41
42namespace HeuristicLab.Problems.DynamicalSystemsModelling {
43  [Item("Dynamical Systems Modelling Problem", "TODO")]
44  [Creatable(CreatableAttribute.Categories.GeneticProgrammingProblems, Priority = 900)]
45  [StorableType("065C6A61-773A-42C9-9DE5-61A5D1D823EB")]
46  public sealed class Problem : SingleObjectiveBasicProblem<MultiEncoding>, IRegressionProblem, IProblemInstanceConsumer<Problem> {
47    #region parameter names
48    private const string ProblemDataParameterName = "Data";
49    private const string TargetVariablesParameterName = "Target variables";
50    private const string FunctionSetParameterName = "Function set";
51    private const string MaximumLengthParameterName = "Size limit";
52    private const string MaximumPretuningParameterOptimizationIterationsParameterName = "Max. pre-tuning parameter optimization iterations";
53    private const string MaximumOdeParameterOptimizationIterationsParameterName = "Max. ODE parameter optimization iterations";
54    private const string NumberOfLatentVariablesParameterName = "Number of latent variables";
55    private const string NumericIntegrationStepsParameterName = "Steps for numeric integration";
56    private const string TrainingEpisodesParameterName = "Training episodes";
57    private const string OptimizeParametersForEpisodesParameterName = "Optimize parameters for episodes";
58    private const string OdeSolverParameterName = "ODE Solver";
59    #endregion
60
61    #region Parameter Properties
62    IParameter IDataAnalysisProblem.ProblemDataParameter { get { return ProblemDataParameter; } }
63
64    public IValueParameter<IRegressionProblemData> ProblemDataParameter {
65      get { return (IValueParameter<IRegressionProblemData>)Parameters[ProblemDataParameterName]; }
66    }
67    public IValueParameter<ReadOnlyCheckedItemList<StringValue>> TargetVariablesParameter {
68      get { return (IValueParameter<ReadOnlyCheckedItemList<StringValue>>)Parameters[TargetVariablesParameterName]; }
69    }
70    public IValueParameter<ReadOnlyCheckedItemList<StringValue>> FunctionSetParameter {
71      get { return (IValueParameter<ReadOnlyCheckedItemList<StringValue>>)Parameters[FunctionSetParameterName]; }
72    }
73    public IFixedValueParameter<IntValue> MaximumLengthParameter {
74      get { return (IFixedValueParameter<IntValue>)Parameters[MaximumLengthParameterName]; }
75    }
76
77    public IFixedValueParameter<IntValue> MaximumPretuningParameterOptimizationIterationsParameter {
78      get { return (IFixedValueParameter<IntValue>)Parameters[MaximumPretuningParameterOptimizationIterationsParameterName]; }
79    }
80    public IFixedValueParameter<IntValue> MaximumOdeParameterOptimizationIterationsParameter {
81      get { return (IFixedValueParameter<IntValue>)Parameters[MaximumOdeParameterOptimizationIterationsParameterName]; }
82    }
83    public IFixedValueParameter<IntValue> NumberOfLatentVariablesParameter {
84      get { return (IFixedValueParameter<IntValue>)Parameters[NumberOfLatentVariablesParameterName]; }
85    }
86    public IFixedValueParameter<IntValue> NumericIntegrationStepsParameter {
87      get { return (IFixedValueParameter<IntValue>)Parameters[NumericIntegrationStepsParameterName]; }
88    }
89    public IValueParameter<ItemList<IntRange>> TrainingEpisodesParameter {
90      get { return (IValueParameter<ItemList<IntRange>>)Parameters[TrainingEpisodesParameterName]; }
91    }
92    public IFixedValueParameter<BoolValue> OptimizeParametersForEpisodesParameter {
93      get { return (IFixedValueParameter<BoolValue>)Parameters[OptimizeParametersForEpisodesParameterName]; }
94    }
95    public IConstrainedValueParameter<StringValue> OdeSolverParameter {
96      get { return (IConstrainedValueParameter<StringValue>)Parameters[OdeSolverParameterName]; }
97    }
98    public IFixedValueParameter<DoubleValue> PretuningErrorWeight {
99      get { return (IFixedValueParameter<DoubleValue>)Parameters["Pretuning NMSE weight"]; }
100    }
101    public IFixedValueParameter<DoubleValue> OdeErrorWeight {
102      get { return (IFixedValueParameter<DoubleValue>)Parameters["ODE NMSE weight"]; }
103    }
104    public IFixedValueParameter<DoubleValue> NumericDifferencesSmoothingParameter {
105      get { return (IFixedValueParameter<DoubleValue>)Parameters["Numeric differences smoothing"]; }
106    }
107    #endregion
108
109    #region Properties
110    public IRegressionProblemData ProblemData {
111      get { return ProblemDataParameter.Value; }
112      set { ProblemDataParameter.Value = value; }
113    }
114    IDataAnalysisProblemData IDataAnalysisProblem.ProblemData { get { return ProblemData; } }
115
116    public ReadOnlyCheckedItemList<StringValue> TargetVariables {
117      get { return TargetVariablesParameter.Value; }
118    }
119
120    public ReadOnlyCheckedItemList<StringValue> FunctionSet {
121      get { return FunctionSetParameter.Value; }
122    }
123
124    public int MaximumLength {
125      get { return MaximumLengthParameter.Value.Value; }
126    }
127    public int MaximumPretuningParameterOptimizationIterations {
128      get { return MaximumPretuningParameterOptimizationIterationsParameter.Value.Value; }
129    }
130    public int MaximumOdeParameterOptimizationIterations {
131      get { return MaximumOdeParameterOptimizationIterationsParameter.Value.Value; }
132    }
133    public int NumberOfLatentVariables {
134      get { return NumberOfLatentVariablesParameter.Value.Value; }
135    }
136    public int NumericIntegrationSteps {
137      get { return NumericIntegrationStepsParameter.Value.Value; }
138    }
139    public IList<IntRange> TrainingEpisodes {
140      get { return TrainingEpisodesParameter.Value; }
141    }
142    public bool OptimizeParametersForEpisodes {
143      get { return OptimizeParametersForEpisodesParameter.Value.Value; }
144    }
145    public double NumericDifferencesSmoothing {
146      get { return NumericDifferencesSmoothingParameter.Value.Value; }
147    }
148
149
150    public string OdeSolver {
151      get { return OdeSolverParameter.Value.Value; }
152      set {
153        var matchingValue = OdeSolverParameter.ValidValues.FirstOrDefault(v => v.Value == value);
154        if (matchingValue == null) throw new ArgumentOutOfRangeException();
155        else OdeSolverParameter.Value = matchingValue;
156      }
157    }
158
159    #endregion
160
161    public event EventHandler ProblemDataChanged;
162
163    public override bool Maximization {
164      get { return false; } // we minimize NMSE
165    }
166
167    #region item cloning and persistence
168    // persistence
169    [StorableConstructor]
170    private Problem(StorableConstructorFlag _) : base(_) { }
171    [StorableHook(HookType.AfterDeserialization)]
172    private void AfterDeserialization() {
173      if (!Parameters.ContainsKey(OptimizeParametersForEpisodesParameterName)) {
174        Parameters.Add(new FixedValueParameter<BoolValue>(OptimizeParametersForEpisodesParameterName, "Flag to select if parameters should be optimized globally or for each episode individually.", new BoolValue(false)));
175      }
176      int iters = 100;
177      if (Parameters.ContainsKey("Max. parameter optimization iterations")) {
178        iters = ((IFixedValueParameter<IntValue>)Parameters["Max. parameter optimization iterations"]).Value.Value;
179      }
180      if (!Parameters.ContainsKey(MaximumPretuningParameterOptimizationIterationsParameterName)) {
181        Parameters.Add(new FixedValueParameter<IntValue>(MaximumPretuningParameterOptimizationIterationsParameterName, "The maximum number of iterations for optimization of parameters of individual equations for numerical derivatives (using L-BFGS). More iterations makes the algorithm slower, fewer iterations might prevent convergence in the optimization scheme. Default = 100", new IntValue(iters)));
182      }
183      if (!Parameters.ContainsKey(MaximumOdeParameterOptimizationIterationsParameterName)) {
184        Parameters.Add(new FixedValueParameter<IntValue>(MaximumOdeParameterOptimizationIterationsParameterName, "The maximum number of iterations for optimization of the full ODE parameters (using L-BFGS). More iterations makes the algorithm slower, fewer iterations might prevent convergence in the optimization scheme. Default = 100", new IntValue(iters)));
185      }
186
187      if (!Parameters.ContainsKey("Pretuning NMSE weight"))
188        Parameters.Add(new FixedValueParameter<DoubleValue>("Pretuning NMSE weight", "For fitness weighting", new DoubleValue(0.5)));
189      if (!Parameters.ContainsKey("ODE NMSE weight"))
190        Parameters.Add(new FixedValueParameter<DoubleValue>("ODE NMSE weight", "For fitness weighting", new DoubleValue(0.5)));
191
192
193      RegisterEventHandlers();
194    }
195
196    // cloning
197    private Problem(Problem original, Cloner cloner)
198      : base(original, cloner) {
199      RegisterEventHandlers();
200    }
201    public override IDeepCloneable Clone(Cloner cloner) { return new Problem(this, cloner); }
202    #endregion
203
204    public Problem()
205      : base() {
206      var targetVariables = new CheckedItemList<StringValue>().AsReadOnly(); // HACK: it would be better to provide a new class derived from IDataAnalysisProblem
207      var functions = CreateFunctionSet();
208      Parameters.Add(new ValueParameter<IRegressionProblemData>(ProblemDataParameterName, "The data captured from the dynamical system. Use CSV import functionality to import data.", new RegressionProblemData()));
209      Parameters.Add(new ValueParameter<ReadOnlyCheckedItemList<StringValue>>(TargetVariablesParameterName, "Target variables (overrides setting in ProblemData)", targetVariables));
210      Parameters.Add(new ValueParameter<ReadOnlyCheckedItemList<StringValue>>(FunctionSetParameterName, "The list of allowed functions", functions));
211      Parameters.Add(new FixedValueParameter<IntValue>(MaximumLengthParameterName, "The maximally allowed length of each expression. Set to a small value (5 - 25). Default = 10", new IntValue(10)));
212      Parameters.Add(new FixedValueParameter<IntValue>(MaximumPretuningParameterOptimizationIterationsParameterName, "The maximum number of iterations for optimization of parameters of individual equations for numerical derivatives (using L-BFGS). More iterations makes the algorithm slower, fewer iterations might prevent convergence in the optimization scheme. Default = 100", new IntValue(100)));
213      Parameters.Add(new FixedValueParameter<IntValue>(MaximumOdeParameterOptimizationIterationsParameterName, "The maximum number of iterations for optimization of the full ODE parameters (using L-BFGS). More iterations makes the algorithm slower, fewer iterations might prevent convergence in the optimization scheme. Default = 100", new IntValue(100)));
214      Parameters.Add(new FixedValueParameter<IntValue>(NumberOfLatentVariablesParameterName, "Latent variables (unobserved variables) allow us to produce expressions which are integrated up and can be used in other expressions. They are handled similarly to target variables in forward simulation / integration. The difference to target variables is that there are no data to which the calculated values of latent variables are compared. Set to a small value (0 .. 5) as necessary (default = 0)", new IntValue(0)));
215      Parameters.Add(new FixedValueParameter<IntValue>(NumericIntegrationStepsParameterName, "Number of steps in the numeric integration that are taken from one row to the next (set to 1 to 100). More steps makes the algorithm slower, less steps worsens the accuracy of the numeric integration scheme.", new IntValue(10)));
216      Parameters.Add(new ValueParameter<ItemList<IntRange>>(TrainingEpisodesParameterName, "A list of ranges that should be used for training, each range represents an independent episode. This overrides the TrainingSet parameter in ProblemData.", new ItemList<IntRange>()));
217      Parameters.Add(new FixedValueParameter<BoolValue>(OptimizeParametersForEpisodesParameterName, "Flag to select if parameters should be optimized globally or for each episode individually.", new BoolValue(false)));
218      Parameters.Add(new FixedValueParameter<DoubleValue>("Pretuning NMSE weight", "For fitness weighting", new DoubleValue(0.5)));
219      Parameters.Add(new FixedValueParameter<DoubleValue>("ODE NMSE weight", "For fitness weighting", new DoubleValue(0.5)));
220      Parameters.Add(new FixedValueParameter<DoubleValue>("Numeric differences smoothing", "Determines the amount of smoothing for the numeric differences which are calculated for pre-tuning. Values from -8 to 8 are reasonable. Use very low value if the data contains no noise. Default: 2.", new DoubleValue(2.0)));
221
222      var solversStr = new string[] { "HeuristicLab" /* , "CVODES" */};
223      var solvers = new ItemSet<StringValue>(
224        solversStr.Select(s => new StringValue(s).AsReadOnly())
225        );
226      Parameters.Add(new ConstrainedValueParameter<StringValue>(OdeSolverParameterName, "The solver to use for solving the initial value ODE problems", solvers, solvers.First()));
227
228      RegisterEventHandlers();
229      InitAllParameters();
230
231      // TODO: use training range as default training episode
232      // TODO: optimization of starting values for latent variables in CVODES solver
233      // TODO: allow to specify the name for the time variable in the dataset and allow variable step-sizes
234    }
235
236    public override double Evaluate(Individual individual, IRandom random) {
237      var trees = individual.Values.Select(v => v.Value).OfType<ISymbolicExpressionTree>().ToArray(); // extract all trees from individual
238
239      var problemData = ProblemData;
240      var targetVars = TargetVariables.CheckedItems.OrderBy(i => i.Index).Select(i => i.Value.Value).ToArray();
241      var latentVariables = Enumerable.Range(1, NumberOfLatentVariables).Select(i => "λ" + i).ToArray(); // TODO: must coincide with the variables which are actually defined in the grammar and also for which we actually have trees
242      if (OptimizeParametersForEpisodes) {
243        throw new NotImplementedException();
244        int eIdx = 0;
245        double totalNMSE = 0.0;
246        int totalSize = 0;
247        foreach (var episode in TrainingEpisodes) {
248          // double[] optTheta;
249          double nmse = OptimizeForEpisodes(trees, problemData, targetVars, latentVariables, random, new[] { episode }, MaximumPretuningParameterOptimizationIterations, NumericIntegrationSteps, OdeSolver, MaximumOdeParameterOptimizationIterations);
250          // individual["OptTheta_" + eIdx] = new DoubleArray(optTheta); // write back optimized parameters so that we can use them in the Analysis method
251          eIdx++;
252          totalNMSE += nmse * episode.Size;
253          totalSize += episode.Size;
254        }
255        return totalNMSE / totalSize;
256      } else {
257        // when no training episodes are specified then we implicitly use the training parition from the problemData
258        var trainingEpisodes = TrainingEpisodes;
259        if (!trainingEpisodes.Any()) {
260          trainingEpisodes = new List<IntRange>();
261          trainingEpisodes.Add((IntRange)ProblemData.TrainingPartition.Clone());
262        }
263        double nmse = OptimizeForEpisodes(trees, problemData, targetVars, latentVariables, random, trainingEpisodes, MaximumPretuningParameterOptimizationIterations, NumericIntegrationSteps, OdeSolver, MaximumOdeParameterOptimizationIterations,
264          PretuningErrorWeight.Value.Value, OdeErrorWeight.Value.Value, NumericDifferencesSmoothing);
265        // individual["OptTheta"] = new DoubleArray(optTheta); // write back optimized parameters so that we can use them in the Analysis method
266        return nmse;
267      }
268    }
269
270    public static double OptimizeForEpisodes(
271      ISymbolicExpressionTree[] trees,
272      IRegressionProblemData problemData,
273      string[] targetVars,
274      string[] latentVariables,
275      IRandom random,
276      IEnumerable<IntRange> episodes,
277      int maxPretuningParameterOptIterations,
278      int numericIntegrationSteps,
279      string odeSolver,
280      int maxOdeParameterOptIterations,
281      double pretuningErrorWeight = 0.5,
282      double odeErrorWeight = 0.5,
283      double numericDifferencesSmoothing = 2
284      ) {
285
286
287
288      // extract constants from trees (without trees for latent variables)
289      var targetVariableTrees = trees.Take(targetVars.Length).ToArray();
290      var latentVariableTrees = trees.Skip(targetVars.Length).ToArray();
291      var constantNodes = targetVariableTrees.Select(t => t.IterateNodesPrefix().OfType<ConstantTreeNode>().ToArray()).ToArray();
292      var initialTheta = constantNodes.Select(nodes => nodes.Select(n => n.Value).ToArray()).ToArray();
293
294      // optimize parameters by fitting f(x,y) to calculated differences dy/dt(t)
295      double nmse = pretuningErrorWeight * PreTuneParameters(trees, problemData, targetVars, latentVariables, random, episodes,
296        maxPretuningParameterOptIterations, numericDifferencesSmoothing,
297        initialTheta, out double[] pretunedParameters);
298
299      // extend parameter vector to include parameters for latent variable trees
300      pretunedParameters = pretunedParameters
301        .Concat(latentVariableTrees
302        .SelectMany(t => t.IterateNodesPrefix().OfType<ConstantTreeNode>().Select(n => n.Value)))
303        .ToArray();
304
305      // optimize parameters using integration of f(x,y) to calculate y(t)
306      nmse += odeErrorWeight * OptimizeParameters(trees, problemData, targetVars, latentVariables, episodes, maxOdeParameterOptIterations, pretunedParameters, numericIntegrationSteps, odeSolver,
307        out double[] optTheta);
308      // var optTheta = pretunedParameters;
309
310      if (double.IsNaN(nmse) ||
311        double.IsInfinity(nmse) ||
312        nmse > 100 * trees.Length * episodes.Sum(ep => ep.Size))
313        return 100 * trees.Length * episodes.Sum(ep => ep.Size);
314
315      // update tree nodes with optimized values
316      var paramIdx = 0;
317      for (var treeIdx = 0; treeIdx < constantNodes.Length; treeIdx++) {
318        for (int i = 0; i < constantNodes[treeIdx].Length; i++)
319          constantNodes[treeIdx][i].Value = optTheta[paramIdx++];
320      }
321      return nmse;
322    }
323
324    private static double PreTuneParameters(
325      ISymbolicExpressionTree[] trees,
326      IRegressionProblemData problemData,
327      string[] targetVars,
328      string[] latentVariables,
329      IRandom random,
330      IEnumerable<IntRange> episodes,
331      int maxParameterOptIterations,
332      double numericDifferencesSmoothing, // for smoothing of numeric differences
333      double[][] initialTheta,
334      out double[] optTheta) {
335      var thetas = new List<double>();
336      double nmse = 0.0;
337      var maxTreeNmse = 100 * episodes.Sum(ep => ep.Size);
338
339      var targetTrees = trees.Take(targetVars.Length).ToArray();
340      var latentTrees = trees.Take(latentVariables.Length).ToArray();
341
342      // first calculate values of latent variables by integration
343      if (latentVariables.Length > 0) {
344        var inputVariables = targetVars.Concat(latentTrees.SelectMany(t => t.IterateNodesPrefix().OfType<VariableTreeNode>().Select(n => n.VariableName))).Except(latentVariables).Distinct();
345        var myState = new OptimizationData(latentTrees, targetVars, inputVariables.ToArray(), problemData, null, episodes.ToArray(), 10, latentVariables, "HeuristicLab");
346
347        var fi = new double[myState.rows.Length * targetVars.Length];
348        var jac = new double[myState.rows.Length * targetVars.Length, myState.nodeValueLookup.ParameterCount];
349        var latentValues = new double[myState.rows.Length, latentVariables.Length];
350        Integrate(myState, fi, jac, latentValues);
351
352        // add integrated latent variables to dataset
353        var modifiedDataset = ((Dataset)problemData.Dataset).ToModifiable();
354        foreach (var variable in latentVariables) {
355          modifiedDataset.AddVariable(variable, Enumerable.Repeat(0.0, modifiedDataset.Rows).ToList()); // empty column
356        }
357        int predIdx = 0;
358        foreach (var ep in episodes) {
359          for (int r = ep.Start; r < ep.End; r++) {
360            for (int latVarIdx = 0; latVarIdx < latentVariables.Length; latVarIdx++) {
361              modifiedDataset.SetVariableValue(latentValues[predIdx, latVarIdx], latentVariables[latVarIdx], r);
362            }
363            predIdx++;
364          }
365        }
366
367        problemData = new RegressionProblemData(modifiedDataset, problemData.AllowedInputVariables, problemData.TargetVariable);
368      }
369      // NOTE: the order of values in parameter matches prefix order of constant nodes in trees
370      for (int treeIdx = 0; treeIdx < targetTrees.Length; treeIdx++) {
371        var t = targetTrees[treeIdx];
372
373        var targetValuesDiff = new List<double>();
374        foreach (var ep in episodes) {
375          var episodeRows = Enumerable.Range(ep.Start, ep.Size);
376          var targetValues = problemData.Dataset.GetDoubleValues(targetVars[treeIdx], episodeRows).ToArray();
377          targetValuesDiff.AddRange(CalculateDifferences(targetValues, numericDifferencesSmoothing));
378        }
379        var adjustedEpisodes = episodes.Select(ep => new IntRange(ep.Start, ep.End));
380
381        // data for input variables is assumed to be known
382        // input variables in pretuning are all target variables and all variable names that occur in the tree
383        var inputVariables = targetVars.Concat(t.IterateNodesPrefix().OfType<VariableTreeNode>().Select(n => n.VariableName)).Distinct();
384
385        var myState = new OptimizationData(new[] { t },
386          targetVars,
387          inputVariables.ToArray(),
388          problemData, new[] { targetValuesDiff.ToArray() }, adjustedEpisodes.ToArray(), -99, latentVariables, string.Empty); // TODO
389        var paramCount = myState.nodeValueLookup.ParameterCount;
390
391        optTheta = initialTheta[treeIdx];
392        if (initialTheta[treeIdx].Length > 0 && maxParameterOptIterations > -1) {
393          try {
394            alglib.minlmstate state;
395            alglib.minlmreport report;
396            var p = new double[initialTheta[treeIdx].Length];
397            var lowerBounds = Enumerable.Repeat(-1000.0, p.Length).ToArray();
398            var upperBounds = Enumerable.Repeat(1000.0, p.Length).ToArray();
399            Array.Copy(initialTheta[treeIdx], p, p.Length);
400            alglib.minlmcreatevj(targetValuesDiff.Count, p, out state);
401            alglib.minlmsetcond(state, 0.0, 0.0, 0.0, maxParameterOptIterations);
402            alglib.minlmsetbc(state, lowerBounds, upperBounds);
403#if DEBUG
404            //alglib.minlmsetgradientcheck(state, 1.0e-7);
405#endif
406            alglib.minlmoptimize(state, EvaluateObjectiveVector, EvaluateObjectiveVectorAndJacobian, null, myState);
407
408            alglib.minlmresults(state, out optTheta, out report);
409            if (report.terminationtype < 0) {
410#if DEBUG
411              if (report.terminationtype == -7) throw new InvalidProgramException("gradient calculation fail!");
412#endif
413              optTheta = initialTheta[treeIdx];
414            }
415          } catch (alglib.alglibexception) {
416            optTheta = initialTheta[treeIdx];
417          }
418        }
419        var tree_nmse = EvaluateMSE(optTheta, myState);
420        if (double.IsNaN(tree_nmse) || double.IsInfinity(tree_nmse) || tree_nmse > maxTreeNmse) {
421          nmse += maxTreeNmse;
422          thetas.AddRange(initialTheta[treeIdx]);
423        } else {
424          nmse += tree_nmse;
425          thetas.AddRange(optTheta);
426        }
427      } // foreach tree
428      optTheta = thetas.ToArray();
429
430      return nmse;
431    }
432
433
434
435    // similar to above but this time we integrate and optimize all parameters for all targets concurrently
436    private static double OptimizeParameters(ISymbolicExpressionTree[] trees, IRegressionProblemData problemData, string[] targetVars, string[] latentVariables,
437      IEnumerable<IntRange> episodes, int maxParameterOptIterations, double[] initialTheta, int numericIntegrationSteps, string odeSolver, out double[] optTheta) {
438      var rowsForDataExtraction = episodes.SelectMany(e => Enumerable.Range(e.Start, e.Size)).ToArray();
439      var targetValues = new double[targetVars.Length][];
440      for (int treeIdx = 0; treeIdx < targetVars.Length; treeIdx++) {
441        var t = trees[treeIdx];
442
443        targetValues[treeIdx] = problemData.Dataset.GetDoubleValues(targetVars[treeIdx], rowsForDataExtraction).ToArray();
444      }
445
446      // data for input variables is assumed to be known
447      // input variables are all variable names that occur in the trees except for target variables (we assume that trees have been generated correctly)
448      var inputVariables = trees.SelectMany(t => t.IterateNodesPrefix().OfType<VariableTreeNode>().Select(n => n.VariableName))
449        .Except(targetVars)
450        .Except(latentVariables)
451        .Distinct();
452
453      var myState = new OptimizationData(trees, targetVars, inputVariables.ToArray(), problemData, targetValues, episodes.ToArray(), numericIntegrationSteps, latentVariables, odeSolver);
454      optTheta = initialTheta;
455
456      if (initialTheta.Length > 0 && maxParameterOptIterations > -1) {
457        var lowerBounds = Enumerable.Repeat(-1000.0, initialTheta.Length).ToArray();
458        var upperBounds = Enumerable.Repeat(1000.0, initialTheta.Length).ToArray();
459        try {
460          alglib.minlmstate state;
461          alglib.minlmreport report;
462          alglib.minlmcreatevj(rowsForDataExtraction.Length * trees.Length, initialTheta, out state);
463          alglib.minlmsetbc(state, lowerBounds, upperBounds);
464          alglib.minlmsetcond(state, 0.0, 0.0, 0.0, maxParameterOptIterations);
465#if DEBUG         
466          //alglib.minlmsetgradientcheck(state, 1.0e-7);
467#endif
468          alglib.minlmoptimize(state, IntegrateAndEvaluateObjectiveVector, IntegrateAndEvaluateObjectiveVectorAndJacobian, null, myState);
469
470          alglib.minlmresults(state, out optTheta, out report);
471
472          if (report.terminationtype < 0) {
473#if DEBUG
474            if (report.terminationtype == -7) throw new InvalidProgramException("gradient calculation fail!");
475#endif            // there was a problem: reset theta and evaluate for inital values
476            optTheta = initialTheta;
477          }
478        } catch (alglib.alglibexception) {
479          optTheta = initialTheta;
480        }
481      }
482      var nmse = EvaluateIntegratedMSE(optTheta, myState);
483      var maxNmse = 100 * targetValues.Length * rowsForDataExtraction.Length;
484      if (double.IsNaN(nmse) || double.IsInfinity(nmse) || nmse > maxNmse) nmse = maxNmse;
485      return nmse;
486    }
487
488
489    // helper
490    public static double EvaluateMSE(double[] x, OptimizationData optimizationData) {
491      var fi = new double[optimizationData.rows.Count()];
492      EvaluateObjectiveVector(x, fi, optimizationData);
493      return fi.Sum(fii => fii * fii) / fi.Length;
494    }
495    public static void EvaluateObjectiveVector(double[] x, double[] fi, object optimizationData) { EvaluateObjectiveVector(x, fi, (OptimizationData)optimizationData); } // for alglib
496    public static void EvaluateObjectiveVector(double[] x, double[] fi, OptimizationData optimizationData) {
497      var rows = optimizationData.rows;
498      var problemData = optimizationData.problemData;
499      var nodeValueLookup = optimizationData.nodeValueLookup;
500      var ds = problemData.Dataset;
501      var variables = optimizationData.variables;
502
503      nodeValueLookup.UpdateParamValues(x);
504
505      int outputIdx = 0;
506      for (int trainIdx = 0; trainIdx < rows.Length; trainIdx++) {
507        // update variable values
508        foreach (var variable in variables) {
509          // in this problem we also allow fixed numeric parameters (represented as variables with the value as name)
510          if (double.TryParse(variable, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) {
511            nodeValueLookup.SetVariableValue(variable, value); // TODO: Perf we don't need to set this for each index
512          } else {
513            nodeValueLookup.SetVariableValue(variable, ds.GetDoubleValue(variable, rows[trainIdx])); // TODO: perf
514          }
515        }
516        // interpret all trees
517        for (int treeIdx = 0; treeIdx < optimizationData.trees.Length; treeIdx++) {
518          var tree = optimizationData.trees[treeIdx];
519          var pred = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValueLookup);
520          var y = optimizationData.targetValues[treeIdx][trainIdx];
521          fi[outputIdx++] = (y - pred) * optimizationData.inverseStandardDeviation[treeIdx];
522        }
523      }
524    }
525
526    public static void EvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, object optimizationData) { EvaluateObjectiveVectorAndJacobian(x, fi, jac, (OptimizationData)optimizationData); } // for alglib
527    public static void EvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, OptimizationData optimizationData) {
528      // extract variable values from dataset
529      var variableValues = new Dictionary<string, Tuple<double, Vector>>();
530      var problemData = optimizationData.problemData;
531      var ds = problemData.Dataset;
532      var rows = optimizationData.rows;
533      var variables = optimizationData.variables;
534
535      var nodeValueLookup = optimizationData.nodeValueLookup;
536      nodeValueLookup.UpdateParamValues(x);
537
538      int termIdx = 0;
539
540      for (int trainIdx = 0; trainIdx < rows.Length; trainIdx++) {
541        // update variable values
542        foreach (var variable in variables) {
543          // in this problem we also allow fixed numeric parameters (represented as variables with the value as name)
544          if (double.TryParse(variable, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) {
545            nodeValueLookup.SetVariableValue(variable, value); // TODO: Perf we don't need to set this for each index
546          } else {
547            nodeValueLookup.SetVariableValue(variable, ds.GetDoubleValue(variable, rows[trainIdx])); // TODO: perf
548          }
549        }
550
551        var calculatedVariables = optimizationData.targetVariables;
552
553        var trees = optimizationData.trees;
554        for (int i = 0; i < trees.Length; i++) {
555          var tree = trees[i];
556          var targetVarName = calculatedVariables[i];
557
558          double f; Vector g;
559          InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValueLookup, out f, out g);
560
561          var y = optimizationData.targetValues[i][trainIdx];
562          fi[termIdx] = (y - f) * optimizationData.inverseStandardDeviation[i]; // scale of NMSE
563          if (jac != null && g != Vector.Zero) for (int j = 0; j < g.Length; j++) jac[termIdx, j] = -g[j] * optimizationData.inverseStandardDeviation[i];
564
565          termIdx++;
566        }
567      }
568
569    }
570
571    // helper
572    public static double EvaluateIntegratedMSE(double[] x, OptimizationData optimizationData) {
573      var fi = new double[optimizationData.rows.Count() * optimizationData.targetVariables.Length];
574      IntegrateAndEvaluateObjectiveVector(x, fi, optimizationData);
575      return fi.Sum(fii => fii * fii) / fi.Length;
576    }
577    public static void IntegrateAndEvaluateObjectiveVector(double[] x, double[] fi, object optimizationData) { IntegrateAndEvaluateObjectiveVector(x, fi, (OptimizationData)optimizationData); } // for alglib
578    public static void IntegrateAndEvaluateObjectiveVector(double[] x, double[] fi, OptimizationData optimizationData) {
579      IntegrateAndEvaluateObjectiveVectorAndJacobian(x, fi, null, optimizationData);
580    }
581
582    public static void IntegrateAndEvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, object optimizationData) { IntegrateAndEvaluateObjectiveVectorAndJacobian(x, fi, jac, (OptimizationData)optimizationData); } // for alglib
583    public static void IntegrateAndEvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, OptimizationData optimizationData) {
584      var rows = optimizationData.rows.ToArray();
585      var problemData = optimizationData.problemData;
586      var nodeValueLookup = optimizationData.nodeValueLookup;
587      var ds = problemData.Dataset;
588      int outputIdx = 0;
589
590      nodeValueLookup.UpdateParamValues(x);
591
592      Integrate(optimizationData, fi, jac, null);
593      var trees = optimizationData.trees;
594
595      // update result with error
596      for (int trainIdx = 0; trainIdx < rows.Length; trainIdx++) {
597        for (int i = 0; i < optimizationData.targetVariables.Length; i++) {
598          var tree = trees[i];
599          var y = optimizationData.targetValues[i][trainIdx];
600          fi[outputIdx] = (y - fi[outputIdx]) * optimizationData.inverseStandardDeviation[i];  // scale for normalized squared error
601          if (jac != null) for (int j = 0; j < x.Length; j++) jac[outputIdx, j] = -jac[outputIdx, j] * optimizationData.inverseStandardDeviation[i];
602          outputIdx++;
603        }
604      }
605    }
606
607    public override void Analyze(Individual[] individuals, double[] qualities, ResultCollection results, IRandom random) {
608      base.Analyze(individuals, qualities, results, random);
609
610      if (!results.ContainsKey("Prediction (training)")) {
611        results.Add(new Result("Prediction (training)", typeof(ReadOnlyItemList<DataTable>)));
612      }
613      if (!results.ContainsKey("Prediction (test)")) {
614        results.Add(new Result("Prediction (test)", typeof(ReadOnlyItemList<DataTable>)));
615      }
616      if (!results.ContainsKey("Models")) {
617        results.Add(new Result("Models", typeof(VariableCollection)));
618      }
619      if (!results.ContainsKey("SNMSE")) {
620        results.Add(new Result("SNMSE", typeof(DoubleValue)));
621      }
622      if(!results.ContainsKey("SNMSE values")) {
623        var dt = new DataTable("SNMSE values");
624        dt.Rows.Add(new DataRow("ODE SNMSE"));
625        dt.Rows.Add(new DataRow("Fitness"));
626        results.Add(new Result("SNMSE values", dt));
627      }
628      if (!results.ContainsKey("Solution")) {
629        results.Add(new Result("Solution", typeof(Solution)));
630      }
631      // if (!results.ContainsKey("Squared error and gradient")) {
632      //   results.Add(new Result("Squared error and gradient", typeof(DataTable)));
633      // }
634
635      // when no training episodes are specified then we implicitly use the training parition from the problemData
636      var trainingEpisodes = TrainingEpisodes;
637      if (!trainingEpisodes.Any()) {
638        trainingEpisodes = new List<IntRange>();
639        trainingEpisodes.Add((IntRange)ProblemData.TrainingPartition.Clone());
640      }
641
642      var bestIndividualAndQuality = this.GetBestIndividual(individuals, qualities);
643      var trees = bestIndividualAndQuality.Item1.Values.Select(v => v.Value).OfType<ISymbolicExpressionTree>().ToArray(); // extract all trees from individual
644
645      results["SNMSE"].Value = new DoubleValue(bestIndividualAndQuality.Item2);
646
647      var problemData = ProblemData;
648      var targetVars = TargetVariables.CheckedItems.OrderBy(i => i.Index).Select(i => i.Value.Value).ToArray();
649      var latentVariables = Enumerable.Range(1, NumberOfLatentVariables).Select(i => "λ" + i).ToArray(); // TODO: must coincide with the variables which are actually defined in the grammar and also for which we actually have trees
650
651      var trainingList = new ItemList<DataTable>();
652
653      if (OptimizeParametersForEpisodes) {
654        throw new NotSupportedException();
655        var eIdx = 0;
656        var trainingPredictions = new List<Tuple<double, Vector>[][]>();
657        foreach (var episode in TrainingEpisodes) {
658          var episodes = new[] { episode };
659          var optimizationData = new OptimizationData(trees, targetVars, problemData.AllowedInputVariables.ToArray(), problemData, null, episodes, NumericIntegrationSteps, latentVariables, OdeSolver);
660          var trainingPrediction = Integrate(optimizationData).ToArray();
661          trainingPredictions.Add(trainingPrediction);
662          eIdx++;
663        }
664
665        // only for target values
666        var trainingRows = TrainingEpisodes.SelectMany(e => Enumerable.Range(e.Start, e.End - e.Start));
667        for (int colIdx = 0; colIdx < targetVars.Length; colIdx++) {
668          var targetVar = targetVars[colIdx];
669          var trainingDataTable = new DataTable(targetVar + " prediction (training)");
670          var actualValuesRow = new DataRow(targetVar, "The values of " + targetVar, problemData.Dataset.GetDoubleValues(targetVar, trainingRows));
671          var predictedValuesRow = new DataRow(targetVar + " pred.", "Predicted values for " + targetVar, trainingPredictions.SelectMany(arr => arr.Select(row => row[colIdx].Item1)).ToArray());
672          trainingDataTable.Rows.Add(actualValuesRow);
673          trainingDataTable.Rows.Add(predictedValuesRow);
674          trainingList.Add(trainingDataTable);
675        }
676        results["Prediction (training)"].Value = trainingList.AsReadOnly();
677
678
679        var models = new VariableCollection();
680
681        foreach (var tup in targetVars.Zip(trees, Tuple.Create)) {
682          var targetVarName = tup.Item1;
683          var tree = tup.Item2;
684
685          var origTreeVar = new HeuristicLab.Core.Variable(targetVarName + "(original)");
686          origTreeVar.Value = (ISymbolicExpressionTree)tree.Clone();
687          models.Add(origTreeVar);
688        }
689        results["Models"].Value = models;
690      } else {
691        // data for input variables is assumed to be known
692        // input variables are all variable names that occur in the trees except for target variables (we assume that trees have been generated correctly)
693        var inputVariables = trees
694          .SelectMany(t => t.IterateNodesPrefix().OfType<VariableTreeNode>().Select(n => n.VariableName))
695          .Except(targetVars)
696          .Except(latentVariables)
697          .Distinct();
698
699        var optimizationData = new OptimizationData(trees, targetVars, inputVariables.ToArray(), problemData, null, trainingEpisodes.ToArray(), NumericIntegrationSteps, latentVariables, OdeSolver);
700        var numParams = optimizationData.nodeValueLookup.ParameterCount;
701
702        var fi = new double[optimizationData.rows.Length * targetVars.Length];
703        var jac = new double[optimizationData.rows.Length * targetVars.Length, numParams];
704        var latentValues = new double[optimizationData.rows.Length, latentVariables.Length];
705        Integrate(optimizationData, fi, jac, latentValues);
706
707
708        // for target values and latent variables
709        var trainingRows = optimizationData.rows;
710        double trainingSNMSE = 0.0;
711        for (int colIdx = 0; colIdx < trees.Length; colIdx++) {
712          // is target variable
713          if (colIdx < targetVars.Length) {
714            var targetVar = targetVars[colIdx];
715            var trainingDataTable = new DataTable(targetVar + " prediction (training)");
716            var actualValuesRow = new DataRow(targetVar, "The values of " + targetVar, problemData.Dataset.GetDoubleValues(targetVar, trainingRows));
717            var idx = Enumerable.Range(0, trainingRows.Length).Select(i => i * targetVars.Length + colIdx);
718            var pred = idx.Select(i => fi[i]);
719            var predictedValuesRow = new DataRow(targetVar + " pred.", "Predicted values for " + targetVar, pred.ToArray());
720            trainingDataTable.Rows.Add(actualValuesRow);
721            trainingDataTable.Rows.Add(predictedValuesRow);
722
723            // again calculate the integrated error (regardless how fitness is determined)
724            trainingSNMSE += actualValuesRow.Values.Zip(predictedValuesRow.Values, (a, p) => Math.Pow(a - p, 2)).Average() / actualValuesRow.Values.Variance() / targetVars.Length;
725
726            for (int paramIdx = 0; paramIdx < numParams; paramIdx++) {
727              var paramSensitivityRow = new DataRow($"∂{targetVar}/∂θ{paramIdx}", $"Sensitivities of parameter {paramIdx}", idx.Select(i => jac[i, paramIdx]).ToArray());
728              paramSensitivityRow.VisualProperties.SecondYAxis = true;
729              trainingDataTable.Rows.Add(paramSensitivityRow);
730            }
731            trainingList.Add(trainingDataTable);
732          } else {
733            var latentVar = latentVariables[colIdx - targetVars.Length];
734            var trainingDataTable = new DataTable(latentVar + " prediction (training)");
735            var idx = Enumerable.Range(0, trainingRows.Length);
736            var pred = idx.Select(i => latentValues[i, colIdx - targetVars.Length]);
737            var predictedValuesRow = new DataRow(latentVar + " pred.", "Predicted values for " + latentVar, pred.ToArray());
738            var emptyRow = new DataRow(latentVar);
739            trainingDataTable.Rows.Add(emptyRow);
740            trainingDataTable.Rows.Add(predictedValuesRow);
741            trainingList.Add(trainingDataTable);
742          }
743        }
744
745        results.AddOrUpdateResult("ODE SNMSE", new DoubleValue(trainingSNMSE));
746        var odeSNMSETable = (DataTable)results["SNMSE values"].Value;
747        odeSNMSETable.Rows["ODE SNMSE"].Values.Add(trainingSNMSE);
748        odeSNMSETable.Rows["Fitness"].Values.Add(bestIndividualAndQuality.Item2);
749
750        // var errorTable = new DataTable("Squared error and gradient");
751        // var seRow = new DataRow("Squared error");
752        // var gradientRows = Enumerable.Range(0, numParams).Select(i => new DataRow($"∂SE/∂θ{i}")).ToArray();
753        // errorTable.Rows.Add(seRow);
754        // foreach (var gRow in gradientRows) {
755        //   gRow.VisualProperties.SecondYAxis = true;
756        //   errorTable.Rows.Add(gRow);
757        // }
758        // var targetValues = targetVars.Select(v => problemData.Dataset.GetDoubleValues(v, trainingRows).ToArray()).ToArray();
759        // int r = 0;
760
761        // foreach (var y_pred in fi) {
762        //   // calculate objective function gradient
763        //   double f_i = 0.0;
764        //   Vector g_i = Vector.CreateNew(new double[numParams]);
765        //   for (int colIdx = 0; colIdx < targetVars.Length; colIdx++) {
766        //     var y_pred_f = y_pred[colIdx].Item1;
767        //     var y = targetValues[colIdx][r];
768        //
769        //     var res = (y - y_pred_f) * optimizationData.inverseStandardDeviation[colIdx];
770        //     var ressq = res * res;
771        //     f_i += ressq;
772        //     g_i.Add(y_pred[colIdx].Item2.Scale(-2.0 * res));
773        //   }
774        //   seRow.Values.Add(f_i);
775        //   for (int j = 0; j < g_i.Length; j++) gradientRows[j].Values.Add(g_i[j]);
776        //   r++;
777        // }
778        // results["Squared error and gradient"].Value = errorTable;
779
780        // only if there is a non-empty test partition
781        if (ProblemData.TestIndices.Any()) {
782          // TODO: DRY for training and test
783
784          var testList = new ItemList<DataTable>();
785          var testRows = ProblemData.TestIndices.ToArray();
786          var testOptimizationData = new OptimizationData(trees, targetVars, problemData.AllowedInputVariables.ToArray(), problemData, null, new IntRange[] { ProblemData.TestPartition }, NumericIntegrationSteps, latentVariables, OdeSolver);
787          var testPrediction = Integrate(testOptimizationData).ToArray();
788
789          for (int colIdx = 0; colIdx < trees.Length; colIdx++) {
790            // is target variable
791            if (colIdx < targetVars.Length) {
792              var targetVar = targetVars[colIdx];
793              var testDataTable = new DataTable(targetVar + " prediction (test)");
794              var actualValuesRow = new DataRow(targetVar, "The values of " + targetVar, problemData.Dataset.GetDoubleValues(targetVar, testRows));
795              var predictedValuesRow = new DataRow(targetVar + " pred.", "Predicted values for " + targetVar, testPrediction.Select(arr => arr[colIdx].Item1).ToArray());
796              testDataTable.Rows.Add(actualValuesRow);
797              testDataTable.Rows.Add(predictedValuesRow);
798              testList.Add(testDataTable);
799
800            } else {
801              // var latentVar = latentVariables[colIdx - targetVars.Length];
802              // var testDataTable = new DataTable(latentVar + " prediction (test)");
803              // var predictedValuesRow = new DataRow(latentVar + " pred.", "Predicted values for " + latentVar, testPrediction.Select(arr => arr[colIdx].Item1).ToArray());
804              // var emptyRow = new DataRow(latentVar);
805              // testDataTable.Rows.Add(emptyRow);
806              // testDataTable.Rows.Add(predictedValuesRow);
807              // testList.Add(testDataTable);
808            }
809          }
810
811          results["Prediction (training)"].Value = trainingList.AsReadOnly();
812          results["Prediction (test)"].Value = testList.AsReadOnly();
813
814        }
815
816        #region simplification of models
817        // TODO the dependency of HeuristicLab.Problems.DataAnalysis.Symbolic is not ideal
818        var models = new VariableCollection();    // to store target var names and original version of tree
819
820        var clonedTrees = new List<ISymbolicExpressionTree>();
821        for (int idx = 0; idx < trees.Length; idx++) {
822          clonedTrees.Add((ISymbolicExpressionTree)trees[idx].Clone());
823        }
824        var ds = problemData.Dataset;
825        var newProblemData = new RegressionProblemData((IDataset)ds.Clone(), problemData.AllowedInputVariables, problemData.TargetVariable);
826        results["Solution"].Value = new Solution(clonedTrees.ToArray(),
827                   // optTheta,
828                   newProblemData,
829                   targetVars,
830                   latentVariables,
831                   trainingEpisodes,
832                   OdeSolver,
833                   NumericIntegrationSteps);
834
835
836        for (int idx = 0; idx < trees.Length; idx++) {
837          var varName = string.Empty;
838          if (idx < targetVars.Length) {
839            varName = targetVars[idx];
840          } else {
841            varName = latentVariables[idx - targetVars.Length];
842          }
843          var tree = trees[idx];
844
845          var origTreeVar = new HeuristicLab.Core.Variable(varName + "(original)");
846          origTreeVar.Value = (ISymbolicExpressionTree)tree.Clone();
847          models.Add(origTreeVar);
848          var simplifiedTreeVar = new HeuristicLab.Core.Variable(varName + "(simplified)");
849          simplifiedTreeVar.Value = TreeSimplifier.Simplify(tree);
850          models.Add(simplifiedTreeVar);
851        }
852
853        results["Models"].Value = models;
854        #endregion
855
856        #region produce classical solutions to allow visualization with PDP
857        for (int treeIdx = 0; treeIdx < targetVars.Length; treeIdx++) {
858          var t = (ISymbolicExpressionTree)trees[treeIdx].Clone();
859          var name = targetVars.Concat(latentVariables).ElementAt(treeIdx); // whatever
860          var model = new SymbolicRegressionModel(name + "_diff", t, new SymbolicDataAnalysisExpressionTreeLinearInterpreter());
861          var solutionDataset = ((Dataset)problemData.Dataset).ToModifiable();
862          var absValues = solutionDataset.GetDoubleValues(name).ToArray();
863
864          solutionDataset.AddVariable(name + "_diff", CalculateDifferences(absValues, NumericDifferencesSmoothing).ToList());
865          var solutionProblemData = new RegressionProblemData(solutionDataset, problemData.AllowedInputVariables, name + "_diff");
866          solutionProblemData.TrainingPartition.Start = problemData.TrainingPartition.Start;
867          solutionProblemData.TrainingPartition.End = problemData.TrainingPartition.End;
868          solutionProblemData.TestPartition.Start = problemData.TestPartition.Start;
869          solutionProblemData.TestPartition.End = problemData.TestPartition.End;
870          var solution = model.CreateRegressionSolution(solutionProblemData);
871          results.AddOrUpdateResult("Solution " + name, solution);
872        }
873        #endregion
874      }
875    }
876
877    #region interpretation
878
879    // the following uses auto-diff to calculate the gradient w.r.t. the parameters forward in time.
880    // this is basically the method described in Gronwall T. Note on the derivatives with respect to a parameter of the solutions of a system of differential equations. Ann. Math. 1919;20:292–296.
881
882    // a comparison of three potential calculation methods for the gradient is given in:
883    // Sengupta, B., Friston, K. J., & Penny, W. D. (2014). Efficient gradient computation for dynamical models. Neuroimage, 98(100), 521–527. http://doi.org/10.1016/j.neuroimage.2014.04.040
884    // "Our comparison establishes that the adjoint method is computationally more efficient for numerical estimation of parametric gradients
885    // for state-space models — both linear and non-linear, as in the case of a dynamical causal model (DCM)"
886
887    // for a solver with the necessary features see: https://computation.llnl.gov/projects/sundials/cvodes
888
889    public static IEnumerable<Tuple<double, Vector>[]> Integrate(OptimizationData optimizationData) {
890      var nTargets = optimizationData.targetVariables.Length;
891      var n = optimizationData.rows.Length * optimizationData.targetVariables.Length;
892      var d = optimizationData.nodeValueLookup.ParameterCount;
893      double[] fi = new double[n];
894      double[,] jac = new double[n, d];
895      Integrate(optimizationData, fi, jac, null);
896      for (int i = 0; i < optimizationData.rows.Length; i++) {
897        var res = new Tuple<double, Vector>[nTargets];
898        for (int j = 0; j < nTargets; j++) {
899          res[j] = Tuple.Create(fi[i * nTargets + j], Vector.CreateFromMatrixRow(jac, i * nTargets + j));
900        }
901        yield return res;
902      }
903    }
904
905    public static void Integrate(OptimizationData optimizationData, double[] fi, double[,] jac, double[,] latentValues) {
906      var trees = optimizationData.trees;
907      var dataset = optimizationData.problemData.Dataset;
908      var inputVariables = optimizationData.variables;
909      var targetVariables = optimizationData.targetVariables;
910      var latentVariables = optimizationData.latentVariables;
911      var episodes = optimizationData.episodes;
912      var odeSolver = optimizationData.odeSolver;
913      var numericIntegrationSteps = optimizationData.numericIntegrationSteps;
914      var calculatedVariables = targetVariables.Concat(latentVariables).ToArray(); // TODO: must conincide with the order of trees in the encoding
915
916
917
918      var nodeValues = optimizationData.nodeValueLookup;
919
920      // TODO: numericIntegrationSteps is only relevant for the HeuristicLab solver
921      var outputRowIdx = 0;
922      var episodeIdx = 0;
923      foreach (var episode in optimizationData.episodes) {
924        var rows = Enumerable.Range(episode.Start, episode.End - episode.Start).ToArray();
925
926        var t0 = rows.First();
927
928        // initialize values for inputs and targets from dataset
929        foreach (var varName in inputVariables) {
930          // in this problem we also allow fixed numeric parameters (represented as variables with the value as name)
931          if (double.TryParse(varName, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) {
932            nodeValues.SetVariableValue(varName, value, Vector.Zero);
933          } else {
934            var y0 = dataset.GetDoubleValue(varName, t0);
935            nodeValues.SetVariableValue(varName, y0, Vector.Zero);
936          }
937        }
938        foreach (var varName in targetVariables) {
939          var y0 = dataset.GetDoubleValue(varName, t0);
940          nodeValues.SetVariableValue(varName, y0, Vector.Zero);
941
942          // output starting value
943          fi[outputRowIdx] = y0;
944          Vector.Zero.CopyTo(jac, outputRowIdx);
945
946          outputRowIdx++;
947        }
948
949        var latentValueRowIdx = 0;
950        var latentValueColIdx = 0;
951        foreach (var varName in latentVariables) {
952          var y0 = 0.0; // assume we start at zero
953          nodeValues.SetVariableValue(varName, y0, Vector.Zero);
954
955          if (latentValues != null) {
956            latentValues[latentValueRowIdx, latentValueColIdx++] = y0;
957          }
958        }
959        latentValueColIdx = 0; latentValueRowIdx++;
960
961        { // CODE BELOW DOESN'T WORK ANYMORE
962          // if (latentVariables.Length > 0) throw new NotImplementedException();
963          //
964          // // add value entries for latent variables which are also integrated
965          // // initial values are at the end of the parameter vector
966          // // separate initial values for each episode
967          // var initialValueIdx = parameterValues.Length - episodes.Count() * latentVariables.Length + episodeIdx * latentVariables.Length;
968          // foreach (var latentVar in latentVariables) {
969          //   var arr = new double[parameterValues.Length]; // backing array
970          //   arr[initialValueIdx] = 1.0;
971          //   var g = new Vector(arr);
972          //   nodeValues.SetVariableValue(latentVar, parameterValues[initialValueIdx], g); // we don't have observations for latent variables therefore we optimize the initial value for each episode
973          //   initialValueIdx++;
974          // }
975        }
976
977        var prevT = t0; // TODO: here we should use a variable for t if it is available. Right now we assume equidistant measurements.
978        foreach (var t in rows.Skip(1)) {
979          if (odeSolver == "HeuristicLab")
980            IntegrateHL(trees, calculatedVariables, nodeValues, numericIntegrationSteps); // integrator updates nodeValues
981          else if (odeSolver == "CVODES")
982            throw new NotImplementedException();
983          // IntegrateCVODES(trees, calculatedVariables, variableValues, parameterValues, t - prevT);
984          else throw new InvalidOperationException("Unknown ODE solver " + odeSolver);
985          prevT = t;
986
987          // update output for target variables (TODO: if we want to visualize the latent variables then we need to provide a separate output)
988          for (int i = 0; i < targetVariables.Length; i++) {
989            var targetVar = targetVariables[i];
990            var yt = nodeValues.GetVariableValue(targetVar);
991
992            // fill up remaining rows with last valid value if there are invalid values
993            if (double.IsNaN(yt.Item1) || double.IsInfinity(yt.Item1)) {
994              for (; outputRowIdx < fi.Length; outputRowIdx++) {
995                var prevIdx = outputRowIdx - targetVariables.Length;
996                fi[outputRowIdx] = fi[prevIdx]; // current <- prev
997                if (jac != null) for (int j = 0; j < jac.GetLength(1); j++) jac[outputRowIdx, j] = jac[prevIdx, j];
998              }
999              return;
1000            };
1001
1002            fi[outputRowIdx] = yt.Item1;
1003            var g = yt.Item2;
1004            g.CopyTo(jac, outputRowIdx);
1005            outputRowIdx++;
1006          }
1007          if (latentValues != null) {
1008            foreach (var latentVariable in latentVariables) {
1009              var lt = nodeValues.GetVariableValue(latentVariable).Item1;
1010              latentValues[latentValueRowIdx, latentValueColIdx++] = lt;
1011            }
1012            latentValueRowIdx++; latentValueColIdx = 0;
1013          }
1014
1015          // update for next time step (only the inputs)
1016          foreach (var varName in inputVariables) {
1017            // in this problem we also allow fixed numeric parameters (represented as variables with the value as name)
1018            if (double.TryParse(varName, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) {
1019              // value is unchanged
1020            } else {
1021              nodeValues.SetVariableValue(varName, dataset.GetDoubleValue(varName, t), Vector.Zero);
1022            }
1023          }
1024        }
1025        episodeIdx++;
1026      }
1027    }
1028
1029    #region CVODES
1030
1031    /*
1032    /// <summary>
1033    ///  Here we use CVODES to solve the ODE. Forward sensitivities are used to calculate the gradient for parameter optimization
1034    /// </summary>
1035    /// <param name="trees">Each equation in the ODE represented as a tree</param>
1036    /// <param name="calculatedVariables">The names of the calculated variables</param>
1037    /// <param name="variableValues">The start values of the calculated variables as well as their sensitivites over parameters</param>
1038    /// <param name="parameterValues">The current parameter values</param>
1039    /// <param name="t">The time t up to which we need to integrate.</param>
1040    private static void IntegrateCVODES(
1041      ISymbolicExpressionTree[] trees, // f(y,p) in tree representation
1042      string[] calculatedVariables, // names of elements of y
1043      Dictionary<string, Tuple<double, Vector>> variableValues,  //  y (intput and output) input: y(t0), output: y(t0+t)
1044      double[] parameterValues, // p
1045      double t // duration t for which we want to integrate
1046      ) {
1047
1048      // the RHS of the ODE
1049      // dy/dt = f(y_t,x_t,p)
1050      CVODES.CVRhsFunc f = CreateOdeRhs(trees, calculatedVariables, parameterValues);
1051      // the Jacobian ∂f/∂y
1052      CVODES.CVDlsJacFunc jac = CreateJac(trees, calculatedVariables, parameterValues);
1053
1054      // the RHS for the forward sensitivities (∂f/∂y)s_i(t) + ∂f/∂p_i
1055      CVODES.CVSensRhsFn sensF = CreateSensitivityRhs(trees, calculatedVariables, parameterValues);
1056
1057      // setup solver
1058      int numberOfEquations = trees.Length;
1059      IntPtr y = IntPtr.Zero;
1060      IntPtr cvode_mem = IntPtr.Zero;
1061      IntPtr A = IntPtr.Zero;
1062      IntPtr yS0 = IntPtr.Zero;
1063      IntPtr linearSolver = IntPtr.Zero;
1064      var ns = parameterValues.Length; // number of parameters
1065
1066      try {
1067        y = CVODES.N_VNew_Serial(numberOfEquations);
1068        // init y to current values of variables
1069        // y must be initialized before calling CVodeInit
1070        for (int i = 0; i < calculatedVariables.Length; i++) {
1071          CVODES.NV_Set_Ith_S(y, i, variableValues[calculatedVariables[i]].Item1);
1072        }
1073
1074        cvode_mem = CVODES.CVodeCreate(CVODES.MultistepMethod.CV_ADAMS, CVODES.NonlinearSolverIteration.CV_FUNCTIONAL);
1075
1076        var flag = CVODES.CVodeInit(cvode_mem, f, 0.0, y);
1077        Assert(CVODES.CV_SUCCESS == flag);
1078
1079        double relTol = 1.0e-2;
1080        double absTol = 1.0;
1081        flag = CVODES.CVodeSStolerances(cvode_mem, relTol, absTol);  // TODO: probably need to adjust absTol per variable
1082        Assert(CVODES.CV_SUCCESS == flag);
1083
1084        A = CVODES.SUNDenseMatrix(numberOfEquations, numberOfEquations);
1085        Assert(A != IntPtr.Zero);
1086
1087        linearSolver = CVODES.SUNDenseLinearSolver(y, A);
1088        Assert(linearSolver != IntPtr.Zero);
1089
1090        flag = CVODES.CVDlsSetLinearSolver(cvode_mem, linearSolver, A);
1091        Assert(CVODES.CV_SUCCESS == flag);
1092
1093        flag = CVODES.CVDlsSetJacFn(cvode_mem, jac);
1094        Assert(CVODES.CV_SUCCESS == flag);
1095
1096        yS0 = CVODES.N_VCloneVectorArray_Serial(ns, y); // clone the output vector for each parameter
1097        unsafe {
1098          // set to initial sensitivities supplied by caller
1099          for (int pIdx = 0; pIdx < ns; pIdx++) {
1100            var yS0_i = *((IntPtr*)yS0.ToPointer() + pIdx);
1101            for (var varIdx = 0; varIdx < calculatedVariables.Length; varIdx++) {
1102              CVODES.NV_Set_Ith_S(yS0_i, varIdx, variableValues[calculatedVariables[varIdx]].Item2[pIdx]);
1103            }
1104          }
1105        }
1106
1107        flag = CVODES.CVodeSensInit(cvode_mem, ns, CVODES.CV_SIMULTANEOUS, sensF, yS0);
1108        Assert(CVODES.CV_SUCCESS == flag);
1109
1110        flag = CVODES.CVodeSensEEtolerances(cvode_mem);
1111        Assert(CVODES.CV_SUCCESS == flag);
1112
1113        // make one forward integration step
1114        double tout = 0.0; // first output time
1115        flag = CVODES.CVode(cvode_mem, t, y, ref tout, CVODES.CV_NORMAL);
1116        if (flag == CVODES.CV_SUCCESS) {
1117          Assert(t == tout);
1118
1119          // get sensitivities
1120          flag = CVODES.CVodeGetSens(cvode_mem, ref tout, yS0);
1121          Assert(CVODES.CV_SUCCESS == flag);
1122
1123          // update variableValues based on integration results
1124          for (int varIdx = 0; varIdx < calculatedVariables.Length; varIdx++) {
1125            var yi = CVODES.NV_Get_Ith_S(y, varIdx);
1126            var gArr = new double[parameterValues.Length];
1127            for (var pIdx = 0; pIdx < parameterValues.Length; pIdx++) {
1128              unsafe {
1129                var yS0_pi = *((IntPtr*)yS0.ToPointer() + pIdx);
1130                gArr[pIdx] = CVODES.NV_Get_Ith_S(yS0_pi, varIdx);
1131              }
1132            }
1133            variableValues[calculatedVariables[varIdx]] = Tuple.Create(yi, new Vector(gArr));
1134          }
1135        } else {
1136          variableValues.Clear();   // indicate problems by not returning new values
1137        }
1138
1139        // cleanup all allocated objects
1140      } finally {
1141        if (y != IntPtr.Zero) CVODES.N_VDestroy_Serial(y);
1142        if (cvode_mem != IntPtr.Zero) CVODES.CVodeFree(ref cvode_mem);
1143        if (linearSolver != IntPtr.Zero) CVODES.SUNLinSolFree(linearSolver);
1144        if (A != IntPtr.Zero) CVODES.SUNMatDestroy(A);
1145        if (yS0 != IntPtr.Zero) CVODES.N_VDestroyVectorArray_Serial(yS0, ns);
1146      }
1147    }
1148
1149
1150    private static CVODES.CVRhsFunc CreateOdeRhs(
1151      ISymbolicExpressionTree[] trees,
1152      string[] calculatedVariables,
1153      double[] parameterValues) {
1154      // we don't need to calculate a gradient here
1155      return (double t,
1156              IntPtr y, // N_Vector, current value of y (input)
1157              IntPtr ydot, // N_Vector (calculated value of y' (output)
1158              IntPtr user_data // optional user data, (unused here)
1159              ) => {
1160                // TODO: perf
1161                var nodeValues = new Dictionary<ISymbolicExpressionTreeNode, Tuple<double, Vector>>();
1162
1163                int pIdx = 0;
1164                foreach (var tree in trees) {
1165                  foreach (var n in tree.IterateNodesPrefix()) {
1166                    if (IsConstantNode(n)) {
1167                      nodeValues.Add(n, Tuple.Create(parameterValues[pIdx], Vector.Zero)); // here we do not need a gradient
1168                      pIdx++;
1169                    } else if (n.SubtreeCount == 0) {
1170                      // for variables and latent variables get the value from variableValues
1171                      var varName = n.Symbol.Name;
1172                      var varIdx = Array.IndexOf(calculatedVariables, varName); // TODO: perf!
1173                      if (varIdx < 0) throw new InvalidProgramException();
1174                      var y_i = CVODES.NV_Get_Ith_S(y, (long)varIdx);
1175                      nodeValues.Add(n, Tuple.Create(y_i, Vector.Zero)); // no gradient needed
1176                    }
1177                  }
1178                }
1179                for (int i = 0; i < trees.Length; i++) {
1180                  var tree = trees[i];
1181                  var res_i = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues);
1182                  CVODES.NV_Set_Ith_S(ydot, i, res_i.Item1);
1183                }
1184                return 0;
1185              };
1186    }
1187
1188    private static CVODES.CVDlsJacFunc CreateJac(
1189      ISymbolicExpressionTree[] trees,
1190      string[] calculatedVariables,
1191      double[] parameterValues) {
1192
1193      return (
1194        double t, // current time (input)
1195        IntPtr y, // N_Vector, current value of y (input)
1196        IntPtr fy, // N_Vector, current value of f (input)
1197        IntPtr Jac, // SUNMatrix ∂f/∂y (output, rows i contains are ∂f_i/∂y vector)
1198        IntPtr user_data, // optional (unused here)
1199        IntPtr tmp1, // N_Vector, optional (unused here)
1200        IntPtr tmp2, // N_Vector, optional (unused here)
1201        IntPtr tmp3 // N_Vector, optional (unused here)
1202      ) => {
1203        // here we need to calculate partial derivatives for the calculated variables y
1204        var nodeValues = new Dictionary<ISymbolicExpressionTreeNode, Tuple<double, Vector>>();
1205        int pIdx = 0;
1206        foreach (var tree in trees) {
1207          foreach (var n in tree.IterateNodesPrefix()) {
1208            if (IsConstantNode(n)) {
1209              nodeValues.Add(n, Tuple.Create(parameterValues[pIdx], Vector.Zero)); // here we need a gradient over y which is zero for parameters
1210              pIdx++;
1211            } else if (n.SubtreeCount == 0) {
1212              // for variables and latent variables we use values supplied in y and init gradient vectors accordingly
1213              var varName = n.Symbol.Name;
1214              var varIdx = Array.IndexOf(calculatedVariables, varName); // TODO: perf!
1215              if (varIdx < 0) throw new InvalidProgramException();
1216
1217              var y_i = CVODES.NV_Get_Ith_S(y, (long)varIdx);
1218              var gArr = new double[CVODES.NV_LENGTH_S(y)]; // backing array
1219              gArr[varIdx] = 1.0;
1220              var g = new Vector(gArr);
1221              nodeValues.Add(n, Tuple.Create(y_i, g));
1222            }
1223          }
1224        }
1225
1226        for (int i = 0; i < trees.Length; i++) {
1227          var tree = trees[i];
1228          var res = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues);
1229          var g = res.Item2;
1230          for (int j = 0; j < calculatedVariables.Length; j++) {
1231            CVODES.SUNDenseMatrix_Set(Jac, i, j, g[j]);
1232          }
1233        }
1234        return 0; // on success
1235      };
1236    }
1237
1238
1239    // to calculate sensitivities RHS for all equations at once
1240    // must compute (∂f/∂y)s_i(t) + ∂f/∂p_i and store in ySdot.
1241    // Index i refers to parameters, dimensionality of matrix and vectors is number of equations
1242    private static CVODES.CVSensRhsFn CreateSensitivityRhs(ISymbolicExpressionTree[] trees, string[] calculatedVariables, double[] parameterValues) {
1243      return (
1244              int Ns, // number of parameters
1245              double t, // current time
1246              IntPtr y, // N_Vector y(t) (input)
1247              IntPtr ydot, // N_Vector dy/dt(t) (input)
1248              IntPtr yS, // N_Vector*, one vector for each parameter (input)
1249              IntPtr ySdot, // N_Vector*, one vector for each parameter (output)
1250              IntPtr user_data, // optional (unused here)
1251              IntPtr tmp1, // N_Vector, optional (unused here)
1252              IntPtr tmp2 // N_Vector, optional (unused here)
1253        ) => {
1254          // here we need to calculate partial derivatives for the calculated variables y as well as for the parameters
1255          var nodeValues = new Dictionary<ISymbolicExpressionTreeNode, Tuple<double, Vector>>();
1256          var d = calculatedVariables.Length + parameterValues.Length; // dimensionality of gradient
1257          // first collect variable values
1258          foreach (var tree in trees) {
1259            foreach (var n in tree.IterateNodesPrefix()) {
1260              if (IsVariableNode(n)) {
1261                // for variables and latent variables we use values supplied in y and init gradient vectors accordingly
1262                var varName = n.Symbol.Name;
1263                var varIdx = Array.IndexOf(calculatedVariables, varName); // TODO: perf!
1264                if (varIdx < 0) throw new InvalidProgramException();
1265
1266                var y_i = CVODES.NV_Get_Ith_S(y, (long)varIdx);
1267                var gArr = new double[d]; // backing array
1268                gArr[varIdx] = 1.0;
1269                var g = new Vector(gArr);
1270                nodeValues.Add(n, Tuple.Create(y_i, g));
1271              }
1272            }
1273          }
1274          // then collect constants
1275          int pIdx = 0;
1276          foreach (var tree in trees) {
1277            foreach (var n in tree.IterateNodesPrefix()) {
1278              if (IsConstantNode(n)) {
1279                var gArr = new double[d];
1280                gArr[calculatedVariables.Length + pIdx] = 1.0;
1281                var g = new Vector(gArr);
1282                nodeValues.Add(n, Tuple.Create(parameterValues[pIdx], g));
1283                pIdx++;
1284              }
1285            }
1286          }
1287          // gradient vector is [∂f/∂y_1, ∂f/∂y_2, ... ∂f/∂yN, ∂f/∂p_1 ... ∂f/∂p_K]
1288
1289
1290          for (pIdx = 0; pIdx < Ns; pIdx++) {
1291            unsafe {
1292              var sDot_pi = *((IntPtr*)ySdot.ToPointer() + pIdx);
1293              CVODES.N_VConst_Serial(0.0, sDot_pi);
1294            }
1295          }
1296
1297          for (int i = 0; i < trees.Length; i++) {
1298            var tree = trees[i];
1299            var res = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues);
1300            var g = res.Item2;
1301
1302
1303            // update ySdot = (∂f/∂y)s_i(t) + ∂f/∂p_i
1304
1305            for (pIdx = 0; pIdx < Ns; pIdx++) {
1306              unsafe {
1307                var sDot_pi = *((IntPtr*)ySdot.ToPointer() + pIdx);
1308                var s_pi = *((IntPtr*)yS.ToPointer() + pIdx);
1309
1310                var v = CVODES.NV_Get_Ith_S(sDot_pi, i);
1311                // (∂f/∂y)s_i(t)
1312                var p = 0.0;
1313                for (int yIdx = 0; yIdx < calculatedVariables.Length; yIdx++) {
1314                  p += g[yIdx] * CVODES.NV_Get_Ith_S(s_pi, yIdx);
1315                }
1316                // + ∂f/∂p_i
1317                CVODES.NV_Set_Ith_S(sDot_pi, i, v + p + g[calculatedVariables.Length + pIdx]);
1318              }
1319            }
1320
1321          }
1322          return 0; // on success
1323        };
1324    }
1325    */
1326    #endregion
1327
1328    private static void IntegrateHL(
1329      ISymbolicExpressionTree[] trees,
1330      string[] calculatedVariables, // names of integrated variables
1331      NodeValueLookup nodeValues,
1332      int numericIntegrationSteps) {
1333
1334
1335      double[] deltaF = new double[calculatedVariables.Length];
1336      Vector[] deltaG = new Vector[calculatedVariables.Length];
1337
1338      double h = 1.0 / numericIntegrationSteps;
1339      for (int step = 0; step < numericIntegrationSteps; step++) {
1340
1341        // evaluate all trees
1342        for (int i = 0; i < trees.Length; i++) {
1343          var tree = trees[i];
1344
1345          // Root.GetSubtree(0).GetSubtree(0) skips programRoot and startSymbol
1346          double f; Vector g;
1347          InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues, out f, out g);
1348          deltaF[i] = f;
1349          deltaG[i] = g;
1350        }
1351
1352        // update variableValues for next step, trapezoid integration
1353        for (int i = 0; i < trees.Length; i++) {
1354          var varName = calculatedVariables[i];
1355          var oldVal = nodeValues.GetVariableValue(varName);
1356          nodeValues.SetVariableValue(varName, oldVal.Item1 + h * deltaF[i], oldVal.Item2.Add(deltaG[i].Scale(h)));
1357        }
1358      }
1359    }
1360
1361    // TODO: use an existing interpreter implementation instead
1362    private static double InterpretRec(ISymbolicExpressionTreeNode node, NodeValueLookup nodeValues) {
1363      if (node is ConstantTreeNode) {
1364        return ((ConstantTreeNode)node).Value;
1365      } else if (node is VariableTreeNode) {
1366        return nodeValues.NodeValue(node);
1367      } else if (node.Symbol is Addition) {
1368        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1369        for (int i = 1; i < node.SubtreeCount; i++) {
1370          f += InterpretRec(node.GetSubtree(i), nodeValues);
1371        }
1372        return f;
1373      } else if (node.Symbol is Multiplication) {
1374        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1375        for (int i = 1; i < node.SubtreeCount; i++) {
1376          f *= InterpretRec(node.GetSubtree(i), nodeValues);
1377        }
1378        return f;
1379      } else if (node.Symbol is Subtraction) {
1380        if (node.SubtreeCount == 1) {
1381          return -InterpretRec(node.GetSubtree(0), nodeValues);
1382        } else {
1383          var f = InterpretRec(node.GetSubtree(0), nodeValues);
1384          for (int i = 1; i < node.SubtreeCount; i++) {
1385            f -= InterpretRec(node.GetSubtree(i), nodeValues);
1386          }
1387          return f;
1388        }
1389      } else if (node.Symbol is Division) {
1390        if (node.SubtreeCount == 1) {
1391          var f = InterpretRec(node.GetSubtree(0), nodeValues);
1392          // protected division
1393          if (f.IsAlmost(0.0)) {
1394            return 0;
1395          } else {
1396            return 1.0 / f;
1397          }
1398        } else {
1399          var f = InterpretRec(node.GetSubtree(0), nodeValues);
1400          for (int i = 1; i < node.SubtreeCount; i++) {
1401            var g = InterpretRec(node.GetSubtree(i), nodeValues);
1402            // protected division
1403            if (g.IsAlmost(0.0)) {
1404              return 0;
1405            } else {
1406              f /= g;
1407            }
1408          }
1409          return f;
1410        }
1411      } else if (node.Symbol is Sine) {
1412        Assert(node.SubtreeCount == 1);
1413
1414        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1415        return Math.Sin(f);
1416      } else if (node.Symbol is Cosine) {
1417        Assert(node.SubtreeCount == 1);
1418
1419        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1420        return Math.Cos(f);
1421      } else if (node.Symbol is Square) {
1422        Assert(node.SubtreeCount == 1);
1423
1424        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1425        return f * f;
1426      } else if (node.Symbol is Exponential) {
1427        Assert(node.SubtreeCount == 1);
1428
1429        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1430        return Math.Exp(f);
1431      } else if (node.Symbol is Logarithm) {
1432        Assert(node.SubtreeCount == 1);
1433
1434        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1435        return Math.Log(f);
1436      } else if (node.Symbol is HyperbolicTangent) {
1437        Assert(node.SubtreeCount == 1);
1438
1439        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1440        return Math.Tanh(f);
1441      } else if (node.Symbol is AnalyticQuotient) {
1442        Assert(node.SubtreeCount == 2);
1443
1444        var f = InterpretRec(node.GetSubtree(0), nodeValues);
1445        var g = InterpretRec(node.GetSubtree(1), nodeValues);
1446        return f / Math.Sqrt(1 + g * g);
1447      } else throw new NotSupportedException("unsupported symbol");
1448    }
1449
1450    private static void Assert(bool cond) {
1451#if DEBUG
1452      if (!cond) throw new InvalidOperationException("Assertion failed");
1453#endif
1454    }
1455
1456    private static void InterpretRec(
1457      ISymbolicExpressionTreeNode node,
1458       NodeValueLookup nodeValues,      // contains value and gradient vector for a node (variables and constants only)
1459      out double z,
1460      out Vector dz
1461      ) {
1462      double f, g;
1463      Vector df, dg;
1464      if (node.Symbol is Constant || node.Symbol is Variable) {
1465        z = nodeValues.NodeValue(node);
1466        dz = Vector.CreateNew(nodeValues.NodeGradient(node)); // original gradient vectors are never changed by evaluation
1467      } else if (node.Symbol is Addition) {
1468        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1469        for (int i = 1; i < node.SubtreeCount; i++) {
1470          InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg);
1471          f = f + g;
1472          df = df.Add(dg);
1473        }
1474        z = f;
1475        dz = df;
1476      } else if (node.Symbol is Multiplication) {
1477        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1478        for (int i = 1; i < node.SubtreeCount; i++) {
1479          InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg);
1480          f = f * g;
1481          df = df.Scale(g).Add(dg.Scale(f));  // f'*g + f*g'
1482        }
1483        z = f;
1484        dz = df;
1485      } else if (node.Symbol is Subtraction) {
1486        if (node.SubtreeCount == 1) {
1487          InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1488          z = -f;
1489          dz = df.Scale(-1.0);
1490        } else {
1491          InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1492          for (int i = 1; i < node.SubtreeCount; i++) {
1493            InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg);
1494            f = f - g;
1495            df = df.Subtract(dg);
1496          }
1497          z = f;
1498          dz = df;
1499        }
1500      } else if (node.Symbol is Division) {
1501        if (node.SubtreeCount == 1) {
1502          InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1503          // protected division
1504          if (f.IsAlmost(0.0)) {
1505            z = 0;
1506            dz = Vector.Zero;
1507          } else {
1508            z = 1.0 / f;
1509            dz = df.Scale(-1 * z * z);
1510          }
1511        } else {
1512          InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1513          for (int i = 1; i < node.SubtreeCount; i++) {
1514            InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg);
1515            // protected division
1516            if (g.IsAlmost(0.0)) {
1517              z = 0;
1518              dz = Vector.Zero;
1519              return;
1520            } else {
1521              var inv_g = 1.0 / g;
1522              f = f * inv_g;
1523              df = dg.Scale(-f * inv_g * inv_g).Add(df.Scale(inv_g));
1524            }
1525          }
1526          z = f;
1527          dz = df;
1528        }
1529      } else if (node.Symbol is Sine) {
1530        Assert(node.SubtreeCount == 1);
1531        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1532        z = Math.Sin(f);
1533        dz = df.Scale(Math.Cos(f));
1534      } else if (node.Symbol is Cosine) {
1535        Assert(node.SubtreeCount == 1);
1536        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1537        z = Math.Cos(f);
1538        dz = df.Scale(-Math.Sin(f));
1539      } else if (node.Symbol is Square) {
1540        Assert(node.SubtreeCount == 1);
1541        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1542        z = f * f;
1543        dz = df.Scale(2.0 * f);
1544      } else if (node.Symbol is Exponential) {
1545        Assert(node.SubtreeCount == 1);
1546        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1547        z = Math.Exp(f);
1548        dz = df.Scale(Math.Exp(f));
1549      } else if (node.Symbol is Logarithm) {
1550        Assert(node.SubtreeCount == 1);
1551        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1552        z = Math.Log(f);
1553        dz = df.Scale(1.0 / f);
1554      } else if (node.Symbol is HyperbolicTangent) {
1555        Assert(node.SubtreeCount == 1);
1556        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1557        z = Math.Tanh(f);
1558        dz = df.Scale(1 - z * z); // tanh(f(x))' = f(x)'sech²(f(x)) = f(x)'(1 - tanh²(f(x)))
1559      } else if (node.Symbol is AnalyticQuotient) {
1560        Assert(node.SubtreeCount == 2);
1561        InterpretRec(node.GetSubtree(0), nodeValues, out f, out df);
1562        InterpretRec(node.GetSubtree(1), nodeValues, out g, out dg);
1563        z = f / Math.Sqrt(1 + g * g);
1564        var denom = 1.0 / Math.Pow(1 + g * g, 1.5);
1565        dz = df.Scale(1 + g * g).Subtract(dg.Scale(f * g)).Scale(denom);
1566      } else {
1567        throw new NotSupportedException("unsupported symbol");
1568      }
1569    }
1570
1571    #endregion
1572
1573    #region events
1574    /*
1575     * Dependencies between parameters:
1576     *
1577     * ProblemData
1578     *    |                                                                         
1579     *    V                                                                         
1580     * TargetVariables   FunctionSet    MaximumLength    NumberOfLatentVariables     
1581     *               |   |                 |                   |                     
1582     *               V   V                 |                   |                     
1583     *             Grammar <---------------+-------------------                     
1584     *                |                                                             
1585     *                V                                                             
1586     *            Encoding                                                           
1587     */
1588    private void RegisterEventHandlers() {
1589      ProblemDataParameter.ValueChanged += ProblemDataParameter_ValueChanged;
1590      if (ProblemDataParameter.Value != null) ProblemDataParameter.Value.Changed += ProblemData_Changed;
1591
1592      TargetVariablesParameter.ValueChanged += TargetVariablesParameter_ValueChanged;
1593      if (TargetVariablesParameter.Value != null) TargetVariablesParameter.Value.CheckedItemsChanged += CheckedTargetVariablesChanged;
1594
1595      FunctionSetParameter.ValueChanged += FunctionSetParameter_ValueChanged;
1596      if (FunctionSetParameter.Value != null) FunctionSetParameter.Value.CheckedItemsChanged += CheckedFunctionsChanged;
1597
1598      MaximumLengthParameter.Value.ValueChanged += MaximumLengthChanged;
1599
1600      NumberOfLatentVariablesParameter.Value.ValueChanged += NumLatentVariablesChanged;
1601    }
1602
1603    private void NumLatentVariablesChanged(object sender, EventArgs e) {
1604      UpdateGrammarAndEncoding();
1605    }
1606
1607    private void MaximumLengthChanged(object sender, EventArgs e) {
1608      UpdateGrammarAndEncoding();
1609    }
1610
1611    private void FunctionSetParameter_ValueChanged(object sender, EventArgs e) {
1612      FunctionSetParameter.Value.CheckedItemsChanged += CheckedFunctionsChanged;
1613    }
1614
1615    private void CheckedFunctionsChanged(object sender, CollectionItemsChangedEventArgs<IndexedItem<StringValue>> e) {
1616      UpdateGrammarAndEncoding();
1617    }
1618
1619    private void TargetVariablesParameter_ValueChanged(object sender, EventArgs e) {
1620      TargetVariablesParameter.Value.CheckedItemsChanged += CheckedTargetVariablesChanged;
1621      UpdateGrammarAndEncoding();
1622    }
1623
1624    private void CheckedTargetVariablesChanged(object sender, CollectionItemsChangedEventArgs<IndexedItem<StringValue>> e) {
1625      UpdateGrammarAndEncoding();
1626    }
1627
1628    private void ProblemDataParameter_ValueChanged(object sender, EventArgs e) {
1629      ProblemDataParameter.Value.Changed += ProblemData_Changed;
1630      OnProblemDataChanged();
1631      OnReset();
1632    }
1633
1634    private void ProblemData_Changed(object sender, EventArgs e) {
1635      OnProblemDataChanged();
1636      OnReset();
1637    }
1638
1639    private void OnProblemDataChanged() {
1640      UpdateTargetVariables();        // implicitly updates other dependent parameters
1641      var handler = ProblemDataChanged;
1642      if (handler != null) handler(this, EventArgs.Empty);
1643    }
1644
1645    #endregion
1646
1647    #region  helper
1648
1649    private static double[] CalculateDifferences(double[] targetValues, double numericDifferencesSmoothing) {
1650      var x = Enumerable.Range(0, targetValues.Length).Select(i => (double)i).ToArray();
1651      alglib.spline1dfitpenalized(x, targetValues, targetValues.Length / 2, numericDifferencesSmoothing,
1652        out int info, out alglib.spline1dinterpolant s, out alglib.spline1dfitreport rep);
1653      if (info <= 0) throw new ArgumentException("There was a problem while smoothing numeric differences. Try to use a different smoothing parameter value.");
1654
1655      double[] dy = new double[x.Length];
1656      for (int i = 0; i < x.Length; i++) {
1657        double xi = x[i];
1658        alglib.spline1ddiff(s, xi, out double y, out double dyi, out double d2y);
1659        dy[i] = dyi;
1660      }
1661      return dy;
1662    }
1663
1664    private void InitAllParameters() {
1665      UpdateTargetVariables(); // implicitly updates the grammar and the encoding
1666    }
1667
1668    private ReadOnlyCheckedItemList<StringValue> CreateFunctionSet() {
1669      var l = new CheckedItemList<StringValue>();
1670      l.Add(new StringValue("Addition").AsReadOnly());
1671      l.Add(new StringValue("Multiplication").AsReadOnly());
1672      l.Add(new StringValue("Division").AsReadOnly());
1673      l.Add(new StringValue("Subtraction").AsReadOnly());
1674      l.Add(new StringValue("Sine").AsReadOnly());
1675      l.Add(new StringValue("Cosine").AsReadOnly());
1676      l.Add(new StringValue("Square").AsReadOnly());
1677      l.Add(new StringValue("Logarithm").AsReadOnly());
1678      l.Add(new StringValue("Exponential").AsReadOnly());
1679      l.Add(new StringValue("HyperbolicTangent").AsReadOnly());
1680      l.Add(new StringValue("AnalyticQuotient").AsReadOnly());
1681      return l.AsReadOnly();
1682    }
1683
1684    private static bool IsConstantNode(ISymbolicExpressionTreeNode n) {
1685      return n is ConstantTreeNode;
1686    }
1687    private static double GetConstantValue(ISymbolicExpressionTreeNode n) {
1688      return ((ConstantTreeNode)n).Value;
1689    }
1690    private static bool IsLatentVariableNode(ISymbolicExpressionTreeNode n) {
1691      return n.Symbol.Name[0] == 'λ';
1692    }
1693    private static bool IsVariableNode(ISymbolicExpressionTreeNode n) {
1694      return (n.SubtreeCount == 0) && !IsConstantNode(n) && !IsLatentVariableNode(n);
1695    }
1696    private static string GetVariableName(ISymbolicExpressionTreeNode n) {
1697      return ((VariableTreeNode)n).VariableName;
1698    }
1699
1700    private void UpdateTargetVariables() {
1701      var currentlySelectedVariables = TargetVariables.CheckedItems
1702        .OrderBy(i => i.Index)
1703        .Select(i => i.Value.Value)
1704        .ToArray();
1705
1706      var newVariablesList = new CheckedItemList<StringValue>(ProblemData.Dataset.VariableNames.Select(str => new StringValue(str).AsReadOnly()).ToArray()).AsReadOnly();
1707      var matchingItems = newVariablesList.Where(item => currentlySelectedVariables.Contains(item.Value)).ToArray();
1708      foreach (var item in newVariablesList) {
1709        if (currentlySelectedVariables.Contains(item.Value)) {
1710          newVariablesList.SetItemCheckedState(item, true);
1711        } else {
1712          newVariablesList.SetItemCheckedState(item, false);
1713        }
1714      }
1715      TargetVariablesParameter.Value = newVariablesList;
1716    }
1717
1718    private void UpdateGrammarAndEncoding() {
1719      var encoding = new MultiEncoding();
1720      var g = CreateGrammar();
1721      foreach (var targetVar in TargetVariables.CheckedItems) {
1722        var e = new SymbolicExpressionTreeEncoding(targetVar + "_tree", g, MaximumLength, MaximumLength);
1723        var multiManipulator = e.Operators.Where(op => op is MultiSymbolicExpressionTreeManipulator).First();
1724        var filteredOperators = e.Operators.Where(op => !(op is IManipulator)).ToArray();
1725        // make sure our multi-manipulator is the only manipulator
1726        e.Operators = new IOperator[] { multiManipulator }.Concat(filteredOperators);
1727
1728        // set the crossover probability to reduce likelihood that multiple trees are crossed at the same time
1729        var subtreeCrossovers = e.Operators.OfType<SubtreeCrossover>();
1730        foreach (var xover in subtreeCrossovers) {
1731          xover.CrossoverProbability.Value = 0.3;
1732        }
1733
1734        encoding = encoding.Add(e); // only limit by length
1735      }
1736      for (int i = 1; i <= NumberOfLatentVariables; i++) {
1737        var e = new SymbolicExpressionTreeEncoding("λ" + i + "_tree", g, MaximumLength, MaximumLength);
1738        var multiManipulator = e.Operators.Where(op => op is MultiSymbolicExpressionTreeManipulator).First();
1739        var filteredOperators = e.Operators.Where(op => !(op is IManipulator)).ToArray();
1740        // make sure our multi-manipulator is the only manipulator
1741        e.Operators = new IOperator[] { multiManipulator }.Concat(filteredOperators);
1742
1743        // set the crossover probability to reduce likelihood that multiple trees are crossed at the same time
1744        var subtreeCrossovers = e.Operators.OfType<SubtreeCrossover>();
1745        foreach (var xover in subtreeCrossovers) {
1746          xover.CrossoverProbability.Value = 0.3;
1747        }
1748
1749        encoding = encoding.Add(e);
1750      }
1751      Encoding = encoding;
1752    }
1753
1754    private ISymbolicExpressionGrammar CreateGrammar() {
1755      var grammar = new TypeCoherentExpressionGrammar();
1756      grammar.StartGrammarManipulation();
1757
1758      var problemData = ProblemData;
1759      var ds = problemData.Dataset;
1760      grammar.MaximumFunctionArguments = 0;
1761      grammar.MaximumFunctionDefinitions = 0;
1762      var allowedVariables = problemData.AllowedInputVariables.Concat(TargetVariables.CheckedItems.Select(chk => chk.Value.Value));
1763      foreach (var varSymbol in grammar.Symbols.OfType<HeuristicLab.Problems.DataAnalysis.Symbolic.VariableBase>()) {
1764        if (!varSymbol.Fixed) {
1765          varSymbol.AllVariableNames = problemData.InputVariables.Select(x => x.Value).Where(x => ds.VariableHasType<double>(x));
1766          varSymbol.VariableNames = allowedVariables.Where(x => ds.VariableHasType<double>(x));
1767        }
1768      }
1769      foreach (var factorSymbol in grammar.Symbols.OfType<BinaryFactorVariable>()) {
1770        if (!factorSymbol.Fixed) {
1771          factorSymbol.AllVariableNames = problemData.InputVariables.Select(x => x.Value).Where(x => ds.VariableHasType<string>(x));
1772          factorSymbol.VariableNames = problemData.AllowedInputVariables.Where(x => ds.VariableHasType<string>(x));
1773          factorSymbol.VariableValues = factorSymbol.VariableNames
1774            .ToDictionary(varName => varName, varName => ds.GetStringValues(varName).Distinct().ToList());
1775        }
1776      }
1777      foreach (var factorSymbol in grammar.Symbols.OfType<FactorVariable>()) {
1778        if (!factorSymbol.Fixed) {
1779          factorSymbol.AllVariableNames = problemData.InputVariables.Select(x => x.Value).Where(x => ds.VariableHasType<string>(x));
1780          factorSymbol.VariableNames = problemData.AllowedInputVariables.Where(x => ds.VariableHasType<string>(x));
1781          factorSymbol.VariableValues = factorSymbol.VariableNames
1782            .ToDictionary(varName => varName,
1783            varName => ds.GetStringValues(varName).Distinct()
1784            .Select((n, i) => Tuple.Create(n, i))
1785            .ToDictionary(tup => tup.Item1, tup => tup.Item2));
1786        }
1787      }
1788
1789      grammar.ConfigureAsDefaultRegressionGrammar();
1790
1791      // configure initialization of constants
1792      var constSy = (Constant)grammar.GetSymbol("Constant");
1793      // max and min are only relevant for initialization
1794      constSy.MaxValue = +1.0e-1; // small initial values for constant opt
1795      constSy.MinValue = -1.0e-1;
1796      constSy.MultiplicativeManipulatorSigma = 1.0; // allow large jumps for manipulation
1797      constSy.ManipulatorMu = 0.0;
1798      constSy.ManipulatorSigma = 1.0; // allow large jumps
1799
1800      // configure initialization of variables
1801      var varSy = (Variable)grammar.GetSymbol("Variable");
1802      // fix variable weights to 1.0
1803      varSy.WeightMu = 1.0;
1804      varSy.WeightSigma = 0.0;
1805      varSy.WeightManipulatorMu = 0.0;
1806      varSy.WeightManipulatorSigma = 0.0;
1807      varSy.MultiplicativeWeightManipulatorSigma = 0.0;
1808
1809      foreach (var f in FunctionSet) {
1810        grammar.GetSymbol(f.Value).Enabled = FunctionSet.ItemChecked(f);
1811      }
1812
1813      grammar.FinishedGrammarManipulation();
1814      return grammar;
1815      // // whenever ProblemData is changed we create a new grammar with the necessary symbols
1816      // var g = new SimpleSymbolicExpressionGrammar();
1817      // var unaryFunc = new string[] { "sin", "cos", "sqr" };
1818      // var binaryFunc = new string[] { "+", "-", "*", "%" };
1819      // foreach (var func in unaryFunc) {
1820      //   if (FunctionSet.CheckedItems.Any(ci => ci.Value.Value == func)) g.AddSymbol(func, 1, 1);
1821      // }
1822      // foreach (var func in binaryFunc) {
1823      //   if (FunctionSet.CheckedItems.Any(ci => ci.Value.Value == func)) g.AddSymbol(func, 2, 2);
1824      // }
1825      //
1826      // foreach (var variableName in ProblemData.AllowedInputVariables.Union(TargetVariables.CheckedItems.Select(i => i.Value.Value)))
1827      //   g.AddTerminalSymbol(variableName);
1828      //
1829      // // generate symbols for numeric parameters for which the value is optimized using AutoDiff
1830      // // we generate multiple symbols to balance the probability for selecting a numeric parameter in the generation of random trees
1831      // var numericConstantsFactor = 2.0;
1832      // for (int i = 0; i < numericConstantsFactor * (ProblemData.AllowedInputVariables.Count() + TargetVariables.CheckedItems.Count()); i++) {
1833      //   g.AddTerminalSymbol("θ" + i); // numeric parameter for which the value is optimized using AutoDiff
1834      // }
1835      //
1836      // // generate symbols for latent variables
1837      // for (int i = 1; i <= NumberOfLatentVariables; i++) {
1838      //   g.AddTerminalSymbol("λ" + i); // numeric parameter for which the value is optimized using AutoDiff
1839      // }
1840      //
1841      // return g;
1842    }
1843    #endregion
1844
1845
1846    #region Import
1847    public void Load(Problem problem) {
1848      // transfer parameter values from problem parameter
1849      this.ProblemData = problem.ProblemData;
1850      this.TrainingEpisodesParameter.Value = problem.TrainingEpisodesParameter.Value;
1851      this.TargetVariablesParameter.Value = problem.TargetVariablesParameter.Value;
1852      this.Name = problem.Name;
1853      this.Description = problem.Description;
1854    }
1855    #endregion
1856
1857
1858    // TODO: for integration we only need a part of the data that we need for optimization
1859
1860    public class OptimizationData {
1861      public readonly ISymbolicExpressionTree[] trees;
1862      public readonly string[] targetVariables;
1863      public readonly IRegressionProblemData problemData;
1864      public readonly double[][] targetValues;
1865      public readonly double[] inverseStandardDeviation;
1866      public readonly IntRange[] episodes;
1867      public readonly int numericIntegrationSteps;
1868      public readonly string[] latentVariables;
1869      public readonly string odeSolver;
1870      public readonly NodeValueLookup nodeValueLookup;
1871      public readonly int[] rows;
1872      internal readonly string[] variables;
1873
1874      public OptimizationData(ISymbolicExpressionTree[] trees, string[] targetVars, string[] inputVariables,
1875        IRegressionProblemData problemData,
1876        double[][] targetValues,
1877        IntRange[] episodes,
1878        int numericIntegrationSteps, string[] latentVariables, string odeSolver) {
1879        this.trees = trees;
1880        this.targetVariables = targetVars;
1881        this.problemData = problemData;
1882        this.targetValues = targetValues;
1883        this.variables = inputVariables;
1884        if (targetValues != null) {
1885          this.inverseStandardDeviation = new double[targetValues.Length];
1886          for (int i = 0; i < targetValues.Length; i++) {
1887            // calculate variance for each episode separately and calc the average
1888            var epStartIdx = 0;
1889            var stdevs = new List<double>();
1890            foreach (var ep in episodes) {
1891              var epValues = targetValues[i].Skip(epStartIdx).Take(ep.Size);
1892              stdevs.Add(epValues.StandardDeviation());
1893              epStartIdx += ep.Size;
1894            }
1895            inverseStandardDeviation[i] = 1.0 / stdevs.Average();
1896          }
1897        } else
1898          this.inverseStandardDeviation = Enumerable.Repeat(1.0, trees.Length).ToArray();
1899        this.episodes = episodes;
1900        this.numericIntegrationSteps = numericIntegrationSteps;
1901        this.latentVariables = latentVariables;
1902        this.odeSolver = odeSolver;
1903        this.nodeValueLookup = new NodeValueLookup(trees);
1904        this.rows = episodes.SelectMany(ep => Enumerable.Range(ep.Start, ep.Size)).ToArray();
1905      }
1906    }
1907
1908    public class NodeValueLookup {
1909      private readonly Dictionary<ISymbolicExpressionTreeNode, Tuple<double, Vector>> node2val = new Dictionary<ISymbolicExpressionTreeNode, Tuple<double, Vector>>();
1910      private readonly Dictionary<string, List<ISymbolicExpressionTreeNode>> name2nodes = new Dictionary<string, List<ISymbolicExpressionTreeNode>>();
1911      private readonly ConstantTreeNode[] constantNodes;
1912      private readonly Vector[] constantGradientVectors;
1913
1914      // private readonly Dictionary<int, ISymbolicExpressionTreeNode> paramIdx2node = new Dictionary<int, ISymbolicExpressionTreeNode>();
1915
1916      public double NodeValue(ISymbolicExpressionTreeNode node) => node2val[node].Item1;
1917      public Vector NodeGradient(ISymbolicExpressionTreeNode node) => node2val[node].Item2;
1918
1919      public NodeValueLookup(ISymbolicExpressionTree[] trees) {
1920
1921        this.constantNodes = trees.SelectMany(t => t.IterateNodesPrefix().OfType<ConstantTreeNode>()).ToArray();
1922        constantGradientVectors = new Vector[constantNodes.Length];
1923        for (int paramIdx = 0; paramIdx < constantNodes.Length; paramIdx++) {
1924          constantGradientVectors[paramIdx] = Vector.CreateIndicator(length: constantNodes.Length, idx: paramIdx);
1925
1926          var node = constantNodes[paramIdx];
1927          node2val[node] = Tuple.Create(node.Value, constantGradientVectors[paramIdx]);
1928        }
1929
1930        foreach (var tree in trees) {
1931          foreach (var node in tree.IterateNodesPrefix().Where(IsVariableNode)) {
1932            var varName = GetVariableName(node);
1933            if (!name2nodes.TryGetValue(varName, out List<ISymbolicExpressionTreeNode> nodes)) {
1934              nodes = new List<ISymbolicExpressionTreeNode>();
1935              name2nodes.Add(varName, nodes);
1936            }
1937            nodes.Add(node);
1938            SetVariableValue(varName, 0.0);  // this value is updated in the prediction loop
1939          }
1940        }
1941      }
1942
1943      public int ParameterCount => constantNodes.Length;
1944
1945      public void SetVariableValue(string variableName, double val) {
1946        SetVariableValue(variableName, val, Vector.Zero);
1947      }
1948      public Tuple<double, Vector> GetVariableValue(string variableName) {
1949        return node2val[name2nodes[variableName].First()];
1950      }
1951      public void SetVariableValue(string variableName, double val, Vector dVal) {
1952        if (name2nodes.TryGetValue(variableName, out List<ISymbolicExpressionTreeNode> nodes)) {
1953          nodes.ForEach(n => node2val[n] = Tuple.Create(val, dVal));
1954        } else {
1955          var fakeNode = new VariableTreeNode(new Variable());
1956          fakeNode.Weight = 1.0;
1957          fakeNode.VariableName = variableName;
1958          var newNodeList = new List<ISymbolicExpressionTreeNode>();
1959          newNodeList.Add(fakeNode);
1960          name2nodes.Add(variableName, newNodeList);
1961          node2val[fakeNode] = Tuple.Create(val, dVal);
1962        }
1963      }
1964
1965      internal void UpdateParamValues(double[] x) {
1966        for (int i = 0; i < x.Length; i++) {
1967          constantNodes[i].Value = x[i];
1968          node2val[constantNodes[i]] = Tuple.Create(x[i], constantGradientVectors[i]);
1969        }
1970      }
1971    }
1972  }
1973}
Note: See TracBrowser for help on using the repository browser.