Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2994-AutoDiffForIntervals/HeuristicLab.Problems.DataAnalysis.Regression.Symbolic.Extensions/ConstrainedNLSInternal.cs @ 17877

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

#2994 made some fixes in the const-opt evaluator with constraints and added some debugging capabilities

File size: 33.3 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Linq;
5using System.Runtime.InteropServices;
6using HeuristicLab.Common;
7using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
8using HeuristicLab.Problems.DataAnalysis.Symbolic.Regression.Extensions;
9
10namespace HeuristicLab.Problems.DataAnalysis.Symbolic.Regression {
11  internal class ConstrainedNLSInternal : IDisposable {
12    private readonly int maxIterations;
13    public int MaxIterations => maxIterations;
14
15    private readonly string solver;
16    public string Solver => solver;
17
18    private readonly ISymbolicExpressionTree expr;
19    public ISymbolicExpressionTree Expr => expr;
20
21    private readonly IRegressionProblemData problemData;
22
23    public IRegressionProblemData ProblemData => problemData;
24
25
26    public event Action FunctionEvaluated;
27    public event Action<int, double> ConstraintEvaluated;
28
29    private double bestError = double.MaxValue;
30    public double BestError => bestError;
31
32    private double curError = double.MaxValue;
33    public double CurError => curError;
34
35    private double[] bestSolution;
36    public double[] BestSolution => bestSolution;
37
38    private ISymbolicExpressionTree bestTree;
39    public ISymbolicExpressionTree BestTree => bestTree;
40
41    private double[] bestConstraintValues;
42    public double[] BestConstraintValues => bestConstraintValues;
43
44    public NLOpt.nlopt_result OptResult { get; private set; }
45
46    private bool disposed = false;
47
48
49    // for debugging (must be in the same order as processed below)
50    public IEnumerable<string> ConstraintDescriptions {
51      get {
52        foreach (var elem in problemData.IntervalConstraints.Constraints) {
53          if (!elem.Enabled) continue;
54          if (elem.Interval.UpperBound < double.PositiveInfinity) {
55            yield return elem.Expression + " < " + elem.Interval.UpperBound;
56          }
57          if (elem.Interval.LowerBound > double.NegativeInfinity) {
58            yield return "-" + elem.Expression + " < " + (-1) * elem.Interval.LowerBound;
59          }
60        }
61      }
62    }
63
64    public bool CheckGradient { get; internal set; }
65
66    // begin internal state
67    private IntPtr nlopt;
68    private SymbolicDataAnalysisExpressionTreeLinearInterpreter interpreter;
69    private readonly NLOpt.nlopt_func calculateObjectiveDelegate; // must hold the delegate to prevent GC
70    // private readonly NLOpt.nlopt_precond preconditionDelegate;
71    private readonly IntPtr[] constraintDataPtr; // must hold the objects to prevent GC
72    private readonly NLOpt.nlopt_func[] calculateConstraintDelegates; // must hold the delegates to prevent GC
73    private readonly List<double> thetaValues;
74    private readonly IDictionary<string, Interval> dataIntervals;
75    private readonly int[] trainingRows;
76    private readonly double[] target;
77    private readonly ISymbolicExpressionTree preparedTree;
78    private readonly ISymbolicExpressionTreeNode[] preparedTreeParameterNodes;
79    private readonly List<ConstantTreeNode>[] allThetaNodes;
80    public List<ISymbolicExpressionTree> constraintTrees;    // TODO make local in ctor (public for debugging)
81
82    private readonly double[] fi_eval;
83    private readonly double[,] jac_eval;
84    private readonly ISymbolicExpressionTree scaledTree;
85    private readonly VectorAutoDiffEvaluator autoDiffEval;
86    private readonly VectorEvaluator eval;
87    private readonly bool invalidProblem = false;
88    private readonly double targetVariance;
89
90    // end internal state
91
92
93    // for data exchange to/from optimizer in native code
94    [StructLayout(LayoutKind.Sequential)]
95    private struct ConstraintData {
96      public int Idx;
97      public ISymbolicExpressionTree Tree;
98      public ISymbolicExpressionTreeNode[] ParameterNodes;
99    }
100
101    internal ConstrainedNLSInternal(string solver, ISymbolicExpressionTree expr, int maxIterations, IRegressionProblemData problemData, double ftol_rel = 0, double ftol_abs = 0, double maxTime = 0) {
102      this.solver = solver;
103      this.expr = expr;
104      this.maxIterations = maxIterations;
105      this.problemData = problemData;
106      this.interpreter = new SymbolicDataAnalysisExpressionTreeLinearInterpreter();
107      this.autoDiffEval = new VectorAutoDiffEvaluator();
108      this.eval = new VectorEvaluator();
109
110      CheckGradient = false;
111
112      var intervalConstraints = problemData.IntervalConstraints;
113      dataIntervals = problemData.VariableRanges.GetIntervals();
114      trainingRows = problemData.TrainingIndices.ToArray();
115      // buffers
116      target = problemData.TargetVariableTrainingValues.ToArray();
117      targetVariance = target.VariancePop();
118      var pred = interpreter.GetSymbolicExpressionTreeValues(expr, problemData.Dataset, trainingRows).ToArray();
119
120      // all trees are linearly scaled (to improve GP performance)
121      if (pred.Any(pi => double.IsInfinity(pi) || double.IsNaN(pi)) || pred.StandardDeviationPop() == 0) {
122        invalidProblem = true;
123        bestError = targetVariance;
124        bestSolution = new double[0];
125        bestConstraintValues = new double[0];
126      } else {
127        #region linear scaling
128        var cov = OnlineCovarianceCalculator.Calculate(pred, target, out OnlineCalculatorError covError);
129        var scalingFactor = cov / pred.VariancePop();
130        var offset = target.Average() - scalingFactor * pred.Average();
131
132        scaledTree = CopyAndScaleTree(expr, scalingFactor, offset);
133        #endregion
134
135        // convert constants to variables named theta...
136        var treeForDerivation = ReplaceAndExtractParameters(scaledTree, out List<string> thetaNames, out thetaValues); // copies the tree
137
138        // create trees for relevant derivatives
139        Dictionary<string, ISymbolicExpressionTree> derivatives = new Dictionary<string, ISymbolicExpressionTree>();
140        allThetaNodes = thetaNames.Select(_ => new List<ConstantTreeNode>()).ToArray();
141        constraintTrees = new List<ISymbolicExpressionTree>();
142        foreach (var constraint in intervalConstraints.Constraints) {
143          if (!constraint.Enabled) continue;
144          if (constraint.IsDerivation) {
145            if (!problemData.AllowedInputVariables.Contains(constraint.Variable))
146              throw new ArgumentException($"Invalid constraint: the variable {constraint.Variable} does not exist in the dataset.");
147            var df = DerivativeCalculator.Derive(treeForDerivation, constraint.Variable);
148
149            // NLOpt requires constraint expressions of the form c(x) <= 0
150            // -> we make two expressions, one for the lower bound and one for the upper bound
151
152            if (constraint.Interval.UpperBound < double.PositiveInfinity) {
153              var df_smaller_upper = Subtract((ISymbolicExpressionTree)df.Clone(), CreateConstant(constraint.Interval.UpperBound));
154              // convert variables named theta back to constants
155              var df_prepared = ReplaceVarWithConst(df_smaller_upper, thetaNames, thetaValues, allThetaNodes);
156              constraintTrees.Add(df_prepared);
157            }
158            if (constraint.Interval.LowerBound > double.NegativeInfinity) {
159              var df_larger_lower = Subtract(CreateConstant(constraint.Interval.LowerBound), (ISymbolicExpressionTree)df.Clone());
160              // convert variables named theta back to constants
161              var df_prepared = ReplaceVarWithConst(df_larger_lower, thetaNames, thetaValues, allThetaNodes);
162              constraintTrees.Add(df_prepared);
163            }
164          } else {
165            if (constraint.Interval.UpperBound < double.PositiveInfinity) {
166              var f_smaller_upper = Subtract((ISymbolicExpressionTree)treeForDerivation.Clone(), CreateConstant(constraint.Interval.UpperBound));
167              // convert variables named theta back to constants
168              var df_prepared = ReplaceVarWithConst(f_smaller_upper, thetaNames, thetaValues, allThetaNodes);
169              constraintTrees.Add(df_prepared);
170            }
171            if (constraint.Interval.LowerBound > double.NegativeInfinity) {
172              var f_larger_lower = Subtract(CreateConstant(constraint.Interval.LowerBound), (ISymbolicExpressionTree)treeForDerivation.Clone());
173              // convert variables named theta back to constants
174              var df_prepared = ReplaceVarWithConst(f_larger_lower, thetaNames, thetaValues, allThetaNodes);
175              constraintTrees.Add(df_prepared);
176            }
177          }
178        }
179
180        preparedTree = ReplaceVarWithConst(treeForDerivation, thetaNames, thetaValues, allThetaNodes);
181        preparedTreeParameterNodes = GetParameterNodes(preparedTree, allThetaNodes);
182
183        var dim = thetaValues.Count;
184        fi_eval = new double[target.Length]; // init buffer;
185        jac_eval = new double[target.Length, dim]; // init buffer
186
187
188        var minVal = Math.Min(-1000.0, thetaValues.Min());
189        var maxVal = Math.Max(1000.0, thetaValues.Max());
190        var lb = Enumerable.Repeat(minVal, thetaValues.Count).ToArray();
191        var up = Enumerable.Repeat(maxVal, thetaValues.Count).ToArray();
192        nlopt = NLOpt.nlopt_create(GetSolver(solver), (uint)dim);
193
194        NLOpt.nlopt_set_lower_bounds(nlopt, lb);
195        NLOpt.nlopt_set_upper_bounds(nlopt, up);
196        calculateObjectiveDelegate = new NLOpt.nlopt_func(CalculateObjective); // keep a reference to the delegate (see below)
197        NLOpt.nlopt_set_min_objective(nlopt, calculateObjectiveDelegate, IntPtr.Zero); // --> without preconditioning
198
199        //preconditionDelegate = new NLOpt.nlopt_precond(PreconditionObjective);
200        //NLOpt.nlopt_set_precond_min_objective(nlopt, calculateObjectiveDelegate, preconditionDelegate, IntPtr.Zero);
201
202
203        constraintDataPtr = new IntPtr[constraintTrees.Count];
204        calculateConstraintDelegates = new NLOpt.nlopt_func[constraintTrees.Count]; // make sure we keep a reference to the delegates (otherwise GC will free delegate objects see https://stackoverflow.com/questions/7302045/callback-delegates-being-collected#7302258)
205        for (int i = 0; i < constraintTrees.Count; i++) {
206          var constraintData = new ConstraintData() { Idx = i, Tree = constraintTrees[i], ParameterNodes = GetParameterNodes(constraintTrees[i], allThetaNodes) };
207          constraintDataPtr[i] = Marshal.AllocHGlobal(Marshal.SizeOf<ConstraintData>());
208          Marshal.StructureToPtr(constraintData, constraintDataPtr[i], fDeleteOld: false);
209          calculateConstraintDelegates[i] = new NLOpt.nlopt_func(CalculateConstraint);
210          NLOpt.nlopt_add_inequality_constraint(nlopt, calculateConstraintDelegates[i], constraintDataPtr[i], 1e-8);
211          // NLOpt.nlopt_add_precond_inequality_constraint(nlopt, calculateConstraintDelegates[i], preconditionDelegate, constraintDataPtr[i], 1e-8);
212        }
213
214
215        var x = thetaValues.ToArray();  /* initial parameters */
216                                        // calculate initial quality
217        // calculate constraints of initial solution
218        double[] constraintGrad = new double[x.Length];
219        bestConstraintValues = new double[calculateConstraintDelegates.Length];
220        for (int i = 0; i < calculateConstraintDelegates.Length; i++) {
221          bestConstraintValues[i] = calculateConstraintDelegates[i].Invoke((uint)x.Length, x, constraintGrad, constraintDataPtr[i]);
222        }
223
224        // if all constraints are OK then calculate the initial error (when there is any violation we use the variance)
225        if (bestConstraintValues.Any(c => c > 0)) {
226          bestError = targetVariance;
227        } else {
228          bestError = CalculateObjective((uint)dim, x, null, IntPtr.Zero);
229        }
230
231        NLOpt.nlopt_set_ftol_rel(nlopt, ftol_rel);
232        NLOpt.nlopt_set_ftol_abs(nlopt, ftol_abs);
233        NLOpt.nlopt_set_maxtime(nlopt, maxTime);
234        NLOpt.nlopt_set_maxeval(nlopt, maxIterations);
235      } // end if valid problem
236    }
237
238
239    ~ConstrainedNLSInternal() {
240      Dispose(false);
241    }
242
243
244    public enum OptimizationMode { ReadOnly, UpdateParameters, UpdateParametersAndKeepLinearScaling };
245
246    internal void Optimize(OptimizationMode mode) {
247      if (invalidProblem) return;
248      var x = thetaValues.ToArray();  /* initial guess */
249      double minf = double.MaxValue; /* minimum objective value upon return */
250      OptResult = NLOpt.nlopt_optimize(nlopt, x, ref minf);
251
252      if (OptResult < 0 && OptResult != NLOpt.nlopt_result.NLOPT_FORCED_STOP) {
253        // throw new InvalidOperationException($"NLOpt failed {res} {NLOpt.nlopt_get_errmsg(nlopt)}");
254        return;
255      } else if (!double.IsNaN(minf) && minf < double.MaxValue) {
256
257        // calculate constraints of final solution
258        double[] _ = new double[x.Length];
259        var optimizedConstraintValues = new double[calculateConstraintDelegates.Length];
260        for (int i = 0; i < calculateConstraintDelegates.Length; i++) {
261          optimizedConstraintValues[i] = calculateConstraintDelegates[i].Invoke((uint)x.Length, x, _, constraintDataPtr[i]);
262        }
263
264        // we accept the optimized parameters when either the error has been reduced or at least one of the violated constraints was improved (all constraints < 0 are OK anyway)
265        if (minf < bestError || bestConstraintValues.Zip(optimizedConstraintValues, Tuple.Create).Any(t => t.Item1 > 0 && t.Item1 > t.Item2)) {
266
267          bestConstraintValues = optimizedConstraintValues;
268          bestSolution = x;
269          bestError = Math.Min(minf, targetVariance);  // limit the error to variance
270
271          // update parameters in tree
272          UpdateParametersInTree(scaledTree, x);
273
274          if (mode == OptimizationMode.UpdateParameters) {
275            // update original expression (when called from evaluator we want to write back optimized parameters)
276            var newChild = (ISymbolicExpressionTreeNode)scaledTree.Root.GetSubtree(0).GetSubtree(0).GetSubtree(0).GetSubtree(0).Clone(); // the optimized sub-tree (without scaling nodes), we need to clone again to remove the parameter nodes which are used in multiple trees (and have incorrect parent relations)
277            var oldChild = expr.Root.GetSubtree(0).GetSubtree(0);
278            expr.Root.GetSubtree(0).RemoveSubtree(0); // delete old tree
279            expr.Root.GetSubtree(0).InsertSubtree(0, newChild);
280
281          } else if (mode == OptimizationMode.UpdateParametersAndKeepLinearScaling) {
282            var oldChild = expr.Root.GetSubtree(0).GetSubtree(0);
283            var newChild = (ISymbolicExpressionTreeNode)scaledTree.Root.GetSubtree(0).GetSubtree(0).Clone(); //  we need to clone again to remove the parameter nodes which are used in multiple trees(and have incorrect parent relations)
284            expr.Root.GetSubtree(0).RemoveSubtree(0); // delete old tree
285            expr.Root.GetSubtree(0).InsertSubtree(0, newChild); // insert the optimized sub-tree (including scaling nodes)
286          }
287        }
288      }
289      bestTree = expr;
290    }
291
292
293    double CalculateObjective(uint dim, double[] curX, double[] grad, IntPtr data) {
294      UpdateThetaValues(curX);
295      var sse = 0.0;
296
297      if (grad != null) {
298        autoDiffEval.Evaluate(preparedTree, problemData.Dataset, trainingRows,
299          preparedTreeParameterNodes, fi_eval, jac_eval);
300
301        // calc sum of squared errors and gradient
302        for (int j = 0; j < grad.Length; j++) grad[j] = 0;
303        for (int i = 0; i < target.Length; i++) {
304          var r = target[i] - fi_eval[i];
305          sse += r * r;
306          for (int j = 0; j < grad.Length; j++) {
307            grad[j] -= 2 * r * jac_eval[i, j];
308          }
309        }
310        // average
311        for (int j = 0; j < grad.Length; j++) { grad[j] /= target.Length; }
312
313        #region check gradient
314        if (grad != null && CheckGradient) {
315          for (int i = 0; i < dim; i++) {
316            // make two additional evaluations
317            var xForNumDiff = (double[])curX.Clone();
318            double delta = Math.Abs(xForNumDiff[i] * 1e-5);
319            xForNumDiff[i] += delta;
320            UpdateThetaValues(xForNumDiff);
321            var evalHigh = eval.Evaluate(preparedTree, problemData.Dataset, trainingRows);
322            var mseHigh = MSE(target, evalHigh);
323            xForNumDiff[i] = curX[i] - delta;
324            UpdateThetaValues(xForNumDiff);
325            var evalLow = eval.Evaluate(preparedTree, problemData.Dataset, trainingRows);
326            var mseLow = MSE(target, evalLow);
327
328            var numericDiff = (mseHigh - mseLow) / (2 * delta);
329            var autoDiff = grad[i];
330            if ((Math.Abs(autoDiff) < 1e-10 && Math.Abs(numericDiff) > 1e-2)
331              || (Math.Abs(autoDiff) >= 1e-10 && Math.Abs((numericDiff - autoDiff) / numericDiff) > 1e-2))
332              throw new InvalidProgramException();
333          }
334        }
335        #endregion
336      } else {
337        var eval = new VectorEvaluator();
338        var prediction = eval.Evaluate(preparedTree, problemData.Dataset, trainingRows);
339
340        // calc sum of squared errors
341        sse = 0.0;
342        for (int i = 0; i < target.Length; i++) {
343          var r = target[i] - prediction[i];
344          sse += r * r;
345        }
346      }
347
348      // UpdateBestSolution(sse / target.Length, curX);
349      RaiseFunctionEvaluated();
350
351      if (double.IsNaN(sse)) {
352        if (grad != null) Array.Clear(grad, 0, grad.Length);
353        return double.MaxValue;
354      }
355      return sse / target.Length;
356    }
357
358    // TODO
359    // private void PreconditionObjective(uint n, double[] x, double[] v, double[] vpre, IntPtr data) {
360    //   UpdateThetaValues(x); // calc H(x)
361    //   
362    //   autoDiffEval.Evaluate(preparedTree, problemData.Dataset, trainingRows,
363    //     preparedTreeParameterNodes, fi_eval, jac_eval);
364    //   var k = jac_eval.GetLength(0);
365    //   var h = new double[n, n];
366    //   
367    //   // calc residuals and scale jac_eval
368    //   var f = 2.0 / (k*k);
369    //
370    //   // approximate hessian H(x) = J(x)^T * J(x)
371    //   alglib.rmatrixgemm((int)n, (int)n, k,
372    //     f, jac_eval, 0, 0, 1,  // transposed
373    //     jac_eval, 0, 0, 0,
374    //     0.0, ref h, 0, 0,
375    //     null
376    //     );
377    //   
378    //
379    //   // scale v
380    //   alglib.rmatrixmv((int)n, (int)n, h, 0, 0, 0, v, 0, ref vpre, 0, alglib.serial);
381    //
382    //
383    //   alglib.spdmatrixcholesky(ref h, (int)n, true);
384    //
385    //   var det = alglib.matdet.spdmatrixcholeskydet(h, (int)n, alglib.serial);
386    // }
387
388
389    private double MSE(double[] a, double[] b) {
390      Trace.Assert(a.Length == b.Length);
391      var sse = 0.0;
392      for (int i = 0; i < a.Length; i++) sse += (a[i] - b[i]) * (a[i] - b[i]);
393      return sse / a.Length;
394    }
395
396    private void UpdateBestSolution(double curF, double[] curX) {
397      if (double.IsNaN(curF) || double.IsInfinity(curF)) return;
398      else if (curF < bestError) {
399        bestError = curF;
400        bestSolution = (double[])curX.Clone();
401      }
402      curError = curF;
403    }
404
405    private void UpdateConstraintViolations(int constraintIdx, double value) {
406      if (double.IsNaN(value) || double.IsInfinity(value)) return;
407      RaiseConstraintEvaluated(constraintIdx, value);
408      // else if (curF < bestError) {
409      //   bestError = curF;
410      //   bestSolution = (double[])curX.Clone();
411      // }
412    }
413
414    double CalculateConstraint(uint dim, double[] curX, double[] grad, IntPtr data) {
415      UpdateThetaValues(curX);
416      var intervalEvaluator = new IntervalEvaluator();
417      var refIntervalEvaluator = new IntervalInterpreter();
418
419      var constraintData = Marshal.PtrToStructure<ConstraintData>(data);
420
421      if (grad != null) Array.Clear(grad, 0, grad.Length);
422
423      var interval = intervalEvaluator.Evaluate(constraintData.Tree, dataIntervals, constraintData.ParameterNodes,
424        out double[] lowerGradient, out double[] upperGradient);
425
426      // compare to reference interval calculation
427      // var refInterval = refIntervalEvaluator.GetSymbolicExpressionTreeInterval(constraintData.Tree, dataIntervals);
428      // if (Math.Abs(interval.LowerBound - refInterval.LowerBound) > Math.Abs(interval.LowerBound) * 1e-4) throw new InvalidProgramException($"Intervals don't match. {interval.LowerBound} <> {refInterval.LowerBound}");
429      // if (Math.Abs(interval.UpperBound - refInterval.UpperBound) > Math.Abs(interval.UpperBound) * 1e-4) throw new InvalidProgramException($"Intervals don't match. {interval.UpperBound} <> {refInterval.UpperBound}");
430
431      // we transformed this to a constraint c(x) <= 0, so only the upper bound is relevant for us
432      if (grad != null) for (int j = 0; j < grad.Length; j++) { grad[j] = upperGradient[j]; }
433
434      #region check gradient
435      if (grad != null && CheckGradient)
436        for (int i = 0; i < dim; i++) {
437          // make two additional evaluations
438          var xForNumDiff = (double[])curX.Clone();
439          double delta = Math.Abs(xForNumDiff[i] * 1e-5);
440          xForNumDiff[i] += delta;
441          UpdateThetaValues(xForNumDiff);
442          var evalHigh = intervalEvaluator.Evaluate(constraintData.Tree, dataIntervals, constraintData.ParameterNodes,
443            out double[] unusedLowerGradientHigh, out double[] unusedUpperGradientHigh);
444
445          xForNumDiff[i] = curX[i] - delta;
446          UpdateThetaValues(xForNumDiff);
447          var evalLow = intervalEvaluator.Evaluate(constraintData.Tree, dataIntervals, constraintData.ParameterNodes,
448            out double[] unusedLowerGradientLow, out double[] unusedUpperGradientLow);
449
450          var numericDiff = (evalHigh.UpperBound - evalLow.UpperBound) / (2 * delta);
451          var autoDiff = grad[i];
452
453          if ((Math.Abs(autoDiff) < 1e-10 && Math.Abs(numericDiff) > 1e-2)
454            || (Math.Abs(autoDiff) >= 1e-10 && Math.Abs((numericDiff - autoDiff) / numericDiff) > 1e-2))
455            throw new InvalidProgramException();
456        }
457      #endregion
458
459
460      UpdateConstraintViolations(constraintData.Idx, interval.UpperBound);
461      if (double.IsNaN(interval.UpperBound)) {
462        if (grad != null) Array.Clear(grad, 0, grad.Length);
463        return double.MaxValue;
464      } else return interval.UpperBound;
465    }
466
467
468    void UpdateThetaValues(double[] theta) {
469      for (int i = 0; i < theta.Length; ++i) {
470        foreach (var constNode in allThetaNodes[i]) constNode.Value = theta[i];
471      }
472    }
473
474    internal void RequestStop() {
475      NLOpt.nlopt_set_force_stop(nlopt, 1); // hopefully NLOpt is thread safe  , val must be <> 0 otherwise no effect
476    }
477
478    private void RaiseFunctionEvaluated() {
479      FunctionEvaluated?.Invoke();
480    }
481
482    private void RaiseConstraintEvaluated(int idx, double value) {
483      ConstraintEvaluated?.Invoke(idx, value);
484    }
485
486
487    #region helper
488
489    private static ISymbolicExpressionTree CopyAndScaleTree(ISymbolicExpressionTree tree, double scalingFactor, double offset) {
490      var m = (ISymbolicExpressionTree)tree.Clone();
491
492      var add = MakeNode<Addition>(MakeNode<Multiplication>(m.Root.GetSubtree(0).GetSubtree(0), CreateConstant(scalingFactor)), CreateConstant(offset));
493      m.Root.GetSubtree(0).RemoveSubtree(0);
494      m.Root.GetSubtree(0).AddSubtree(add);
495      return m;
496    }
497
498
499    private NLOpt.nlopt_algorithm GetSolver(string solver) {
500      if (solver.Contains("MMA")) return NLOpt.nlopt_algorithm.NLOPT_LD_MMA;
501      if (solver.Contains("COBYLA")) return NLOpt.nlopt_algorithm.NLOPT_LN_COBYLA;
502      if (solver.Contains("CCSAQ")) return NLOpt.nlopt_algorithm.NLOPT_LD_CCSAQ;
503      if (solver.Contains("ISRES")) return NLOpt.nlopt_algorithm.NLOPT_GN_ISRES;
504
505      if (solver.Contains("DIRECT_G")) return NLOpt.nlopt_algorithm.NLOPT_GN_DIRECT;
506      if (solver.Contains("NLOPT_GN_DIRECT_L")) return NLOpt.nlopt_algorithm.NLOPT_GN_DIRECT_L;
507      if (solver.Contains("NLOPT_GN_DIRECT_L_RAND")) return NLOpt.nlopt_algorithm.NLOPT_GN_DIRECT_L_RAND;
508      if (solver.Contains("NLOPT_GN_ORIG_DIRECT")) return NLOpt.nlopt_algorithm.NLOPT_GN_DIRECT;
509      if (solver.Contains("NLOPT_GN_ORIG_DIRECT_L")) return NLOpt.nlopt_algorithm.NLOPT_GN_ORIG_DIRECT_L;
510      if (solver.Contains("NLOPT_GD_STOGO")) return NLOpt.nlopt_algorithm.NLOPT_GD_STOGO;
511      if (solver.Contains("NLOPT_GD_STOGO_RAND")) return NLOpt.nlopt_algorithm.NLOPT_GD_STOGO_RAND;
512      if (solver.Contains("NLOPT_LD_LBFGS_NOCEDAL")) return NLOpt.nlopt_algorithm.NLOPT_LD_LBFGS_NOCEDAL;
513      if (solver.Contains("NLOPT_LD_LBFGS")) return NLOpt.nlopt_algorithm.NLOPT_LD_LBFGS;
514      if (solver.Contains("NLOPT_LN_PRAXIS")) return NLOpt.nlopt_algorithm.NLOPT_LN_PRAXIS;
515      if (solver.Contains("NLOPT_LD_VAR1")) return NLOpt.nlopt_algorithm.NLOPT_LD_VAR1;
516      if (solver.Contains("NLOPT_LD_VAR2")) return NLOpt.nlopt_algorithm.NLOPT_LD_VAR2;
517      if (solver.Contains("NLOPT_LD_TNEWTON")) return NLOpt.nlopt_algorithm.NLOPT_LD_TNEWTON;
518      if (solver.Contains("NLOPT_LD_TNEWTON_RESTART")) return NLOpt.nlopt_algorithm.NLOPT_LD_TNEWTON_RESTART;
519      if (solver.Contains("NLOPT_LD_TNEWTON_PRECOND")) return NLOpt.nlopt_algorithm.NLOPT_LD_TNEWTON_PRECOND;
520      if (solver.Contains("NLOPT_LD_TNEWTON_PRECOND_RESTART")) return NLOpt.nlopt_algorithm.NLOPT_LD_TNEWTON_PRECOND_RESTART;
521      if (solver.Contains("NLOPT_GN_CRS2_LM")) return NLOpt.nlopt_algorithm.NLOPT_GN_CRS2_LM;
522      if (solver.Contains("NLOPT_GN_MLSL")) return NLOpt.nlopt_algorithm.NLOPT_GN_MLSL;
523      if (solver.Contains("NLOPT_GD_MLSL")) return NLOpt.nlopt_algorithm.NLOPT_GD_MLSL;
524      if (solver.Contains("NLOPT_GN_MLSL_LDS")) return NLOpt.nlopt_algorithm.NLOPT_GN_MLSL_LDS;
525      if (solver.Contains("NLOPT_GD_MLSL_LDS")) return NLOpt.nlopt_algorithm.NLOPT_GD_MLSL_LDS;
526      if (solver.Contains("NLOPT_LN_NEWUOA")) return NLOpt.nlopt_algorithm.NLOPT_LN_NEWUOA;
527      if (solver.Contains("NLOPT_LN_NEWUOA_BOUND")) return NLOpt.nlopt_algorithm.NLOPT_LN_NEWUOA_BOUND;
528      if (solver.Contains("NLOPT_LN_NELDERMEAD")) return NLOpt.nlopt_algorithm.NLOPT_LN_NELDERMEAD;
529      if (solver.Contains("NLOPT_LN_SBPLX")) return NLOpt.nlopt_algorithm.NLOPT_LN_SBPLX;
530      if (solver.Contains("NLOPT_LN_AUGLAG")) return NLOpt.nlopt_algorithm.NLOPT_LN_AUGLAG;
531      if (solver.Contains("NLOPT_LD_AUGLAG")) return NLOpt.nlopt_algorithm.NLOPT_LD_AUGLAG;
532      if (solver.Contains("NLOPT_LN_BOBYQA")) return NLOpt.nlopt_algorithm.NLOPT_LN_BOBYQA;
533      if (solver.Contains("NLOPT_AUGLAG")) return NLOpt.nlopt_algorithm.NLOPT_AUGLAG;
534      if (solver.Contains("NLOPT_LD_SLSQP")) return NLOpt.nlopt_algorithm.NLOPT_LD_SLSQP;
535      if (solver.Contains("NLOPT_LD_CCSAQ))")) return NLOpt.nlopt_algorithm.NLOPT_LD_CCSAQ;
536      if (solver.Contains("NLOPT_GN_ESCH")) return NLOpt.nlopt_algorithm.NLOPT_GN_ESCH;
537      if (solver.Contains("NLOPT_GN_AGS")) return NLOpt.nlopt_algorithm.NLOPT_GN_AGS;
538
539      throw new ArgumentException($"Unknown solver {solver}");
540    }
541
542    // determines the nodes over which we can calculate the partial derivative
543    // this is different from the vector of all parameters because not every tree contains all parameters
544    private static ISymbolicExpressionTreeNode[] GetParameterNodes(ISymbolicExpressionTree tree, List<ConstantTreeNode>[] allNodes) {
545      // TODO better solution necessary
546      var treeConstNodes = tree.IterateNodesPostfix().OfType<ConstantTreeNode>().ToArray();
547      var paramNodes = new ISymbolicExpressionTreeNode[allNodes.Length];
548      for (int i = 0; i < paramNodes.Length; i++) {
549        paramNodes[i] = allNodes[i].SingleOrDefault(n => treeConstNodes.Contains(n));
550      }
551      return paramNodes;
552    }
553
554    private static ISymbolicExpressionTree ReplaceVarWithConst(ISymbolicExpressionTree tree, List<string> thetaNames, List<double> thetaValues, List<ConstantTreeNode>[] thetaNodes) {
555      var copy = (ISymbolicExpressionTree)tree.Clone();
556      var nodes = copy.IterateNodesPostfix().ToList();
557      for (int i = 0; i < nodes.Count; i++) {
558        var n = nodes[i] as VariableTreeNode;
559        if (n != null) {
560          var thetaIdx = thetaNames.IndexOf(n.VariableName);
561          if (thetaIdx >= 0) {
562            var parent = n.Parent;
563            if (thetaNodes[thetaIdx].Any()) {
564              // HACK: REUSE CONSTANT TREE NODE IN SEVERAL TREES
565              // we use this trick to allow autodiff over thetas when thetas occurr multiple times in the tree (e.g. in derived trees)
566              var constNode = thetaNodes[thetaIdx].First();
567              var childIdx = parent.IndexOfSubtree(n);
568              parent.RemoveSubtree(childIdx);
569              parent.InsertSubtree(childIdx, constNode);
570            } else {
571              var constNode = (ConstantTreeNode)CreateConstant(thetaValues[thetaIdx]);
572              var childIdx = parent.IndexOfSubtree(n);
573              parent.RemoveSubtree(childIdx);
574              parent.InsertSubtree(childIdx, constNode);
575              thetaNodes[thetaIdx].Add(constNode);
576            }
577          }
578        }
579      }
580      return copy;
581    }
582
583
584
585
586    private void UpdateParametersInTree(ISymbolicExpressionTree scaledTree, double[] x) {
587      var pIdx = 0;
588      // here we lose the two last parameters (for linear scaling)
589      foreach (var node in scaledTree.IterateNodesPostfix()) {
590        if (node is ConstantTreeNode constTreeNode) {
591          constTreeNode.Value = x[pIdx++];
592        } else if (node is VariableTreeNode varTreeNode) {
593          if (varTreeNode.Weight != 1.0) // see ReplaceAndExtractParameters
594            varTreeNode.Weight = x[pIdx++];
595        }
596      }
597      if (pIdx != x.Length) throw new InvalidProgramException();
598    }
599
600    private static ISymbolicExpressionTree ReplaceAndExtractParameters(ISymbolicExpressionTree tree, out List<string> thetaNames, out List<double> thetaValues) {
601      thetaNames = new List<string>();
602      thetaValues = new List<double>();
603      var copy = (ISymbolicExpressionTree)tree.Clone();
604      var nodes = copy.IterateNodesPostfix().ToList();
605
606      int n = 1;
607      for (int i = 0; i < nodes.Count; ++i) {
608        var node = nodes[i];
609        if (node is ConstantTreeNode constantTreeNode) {
610          var thetaVar = (VariableTreeNode)new Problems.DataAnalysis.Symbolic.Variable().CreateTreeNode();
611          thetaVar.Weight = 1;
612          thetaVar.VariableName = $"θ{n++}";
613
614          thetaNames.Add(thetaVar.VariableName);
615          thetaValues.Add(constantTreeNode.Value);
616
617          var parent = constantTreeNode.Parent;
618          if (parent != null) {
619            var index = constantTreeNode.Parent.IndexOfSubtree(constantTreeNode);
620            parent.RemoveSubtree(index);
621            parent.InsertSubtree(index, thetaVar);
622          }
623        }
624        if (node is VariableTreeNode varTreeNode) {
625          if (varTreeNode.Weight == 1) continue; // NOTE: here we assume that we do not tune variable weights when they are originally exactly 1 because we assume that the tree has been parsed and the tree explicitly has the structure w * var
626
627          var thetaVar = (VariableTreeNode)new Problems.DataAnalysis.Symbolic.Variable().CreateTreeNode();
628          thetaVar.Weight = 1;
629          thetaVar.VariableName = $"θ{n++}";
630
631          thetaNames.Add(thetaVar.VariableName);
632          thetaValues.Add(varTreeNode.Weight);
633
634          var parent = varTreeNode.Parent;
635          if (parent != null) {
636            var index = varTreeNode.Parent.IndexOfSubtree(varTreeNode);
637            parent.RemoveSubtree(index);
638            var prodNode = MakeNode<Multiplication>();
639            varTreeNode.Weight = 1.0;
640            prodNode.AddSubtree(varTreeNode);
641            prodNode.AddSubtree(thetaVar);
642            parent.InsertSubtree(index, prodNode);
643          }
644        }
645      }
646      return copy;
647    }
648
649    private static ISymbolicExpressionTreeNode CreateConstant(double value) {
650      var constantNode = (ConstantTreeNode)new Constant().CreateTreeNode();
651      constantNode.Value = value;
652      return constantNode;
653    }
654
655    private static ISymbolicExpressionTree Subtract(ISymbolicExpressionTree t, ISymbolicExpressionTreeNode b) {
656      var sub = MakeNode<Subtraction>(t.Root.GetSubtree(0).GetSubtree(0), b);
657      t.Root.GetSubtree(0).RemoveSubtree(0);
658      t.Root.GetSubtree(0).InsertSubtree(0, sub);
659      return t;
660    }
661    private static ISymbolicExpressionTree Subtract(ISymbolicExpressionTreeNode b, ISymbolicExpressionTree t) {
662      var sub = MakeNode<Subtraction>(b, t.Root.GetSubtree(0).GetSubtree(0));
663      t.Root.GetSubtree(0).RemoveSubtree(0);
664      t.Root.GetSubtree(0).InsertSubtree(0, sub);
665      return t;
666    }
667
668    private static ISymbolicExpressionTreeNode MakeNode<T>(params ISymbolicExpressionTreeNode[] fs) where T : ISymbol, new() {
669      var node = new T().CreateTreeNode();
670      foreach (var f in fs) node.AddSubtree(f);
671      return node;
672    }
673
674    public void Dispose() {
675      Dispose(true);
676      GC.SuppressFinalize(this);
677    }
678
679    protected virtual void Dispose(bool disposing) {
680      if (disposed)
681        return;
682
683      if (disposing) {
684        // Free any other managed objects here.
685      }
686
687      // Free any unmanaged objects here.
688      if (nlopt != IntPtr.Zero) {
689        NLOpt.nlopt_destroy(nlopt);
690        nlopt = IntPtr.Zero;
691      }
692      if (constraintDataPtr != null) {
693        for (int i = 0; i < constraintDataPtr.Length; i++)
694          if (constraintDataPtr[i] != IntPtr.Zero) {
695            Marshal.DestroyStructure<ConstraintData>(constraintDataPtr[i]);
696            Marshal.FreeHGlobal(constraintDataPtr[i]);
697            constraintDataPtr[i] = IntPtr.Zero;
698          }
699      }
700
701      disposed = true;
702    }
703    #endregion
704  }
705}
Note: See TracBrowser for help on using the repository browser.