#region License Information /* HeuristicLab * Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using HeuristicLab.Analysis; using HeuristicLab.Collections; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; using HeuristicLab.Optimization; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using HeuristicLab.Problems.DataAnalysis; using HeuristicLab.Problems.DataAnalysis.Symbolic; using HeuristicLab.Problems.Instances; using Variable = HeuristicLab.Problems.DataAnalysis.Symbolic.Variable; using HEAL.Attic; using HeuristicLab.Problems.DataAnalysis.Symbolic.Regression; namespace HeuristicLab.Problems.DynamicalSystemsModelling { [Item("Dynamical Systems Modelling Problem", "TODO")] [Creatable(CreatableAttribute.Categories.GeneticProgrammingProblems, Priority = 900)] [StorableType("065C6A61-773A-42C9-9DE5-61A5D1D823EB")] public sealed class Problem : SingleObjectiveBasicProblem, IRegressionProblem, IProblemInstanceConsumer, IProblemInstanceExporter { #region parameter names private const string ProblemDataParameterName = "Data"; private const string TargetVariablesParameterName = "Target variables"; private const string FunctionSetParameterName = "Function set"; private const string MaximumLengthParameterName = "Size limit"; private const string MaximumParameterOptimizationIterationsParameterName = "Max. parameter optimization iterations"; private const string NumberOfLatentVariablesParameterName = "Number of latent variables"; private const string NumericIntegrationStepsParameterName = "Steps for numeric integration"; private const string TrainingEpisodesParameterName = "Training episodes"; private const string OptimizeParametersForEpisodesParameterName = "Optimize parameters for episodes"; private const string OdeSolverParameterName = "ODE Solver"; #endregion #region Parameter Properties IParameter IDataAnalysisProblem.ProblemDataParameter { get { return ProblemDataParameter; } } public IValueParameter ProblemDataParameter { get { return (IValueParameter)Parameters[ProblemDataParameterName]; } } public IValueParameter> TargetVariablesParameter { get { return (IValueParameter>)Parameters[TargetVariablesParameterName]; } } public IValueParameter> FunctionSetParameter { get { return (IValueParameter>)Parameters[FunctionSetParameterName]; } } public IFixedValueParameter MaximumLengthParameter { get { return (IFixedValueParameter)Parameters[MaximumLengthParameterName]; } } public IFixedValueParameter MaximumParameterOptimizationIterationsParameter { get { return (IFixedValueParameter)Parameters[MaximumParameterOptimizationIterationsParameterName]; } } public IFixedValueParameter NumberOfLatentVariablesParameter { get { return (IFixedValueParameter)Parameters[NumberOfLatentVariablesParameterName]; } } public IFixedValueParameter NumericIntegrationStepsParameter { get { return (IFixedValueParameter)Parameters[NumericIntegrationStepsParameterName]; } } public IValueParameter> TrainingEpisodesParameter { get { return (IValueParameter>)Parameters[TrainingEpisodesParameterName]; } } public IFixedValueParameter OptimizeParametersForEpisodesParameter { get { return (IFixedValueParameter)Parameters[OptimizeParametersForEpisodesParameterName]; } } public IConstrainedValueParameter OdeSolverParameter { get { return (IConstrainedValueParameter)Parameters[OdeSolverParameterName]; } } #endregion #region Properties public IRegressionProblemData ProblemData { get { return ProblemDataParameter.Value; } set { ProblemDataParameter.Value = value; } } IDataAnalysisProblemData IDataAnalysisProblem.ProblemData { get { return ProblemData; } } public ReadOnlyCheckedItemList TargetVariables { get { return TargetVariablesParameter.Value; } } public ReadOnlyCheckedItemList FunctionSet { get { return FunctionSetParameter.Value; } } public int MaximumLength { get { return MaximumLengthParameter.Value.Value; } } public int MaximumParameterOptimizationIterations { get { return MaximumParameterOptimizationIterationsParameter.Value.Value; } } public int NumberOfLatentVariables { get { return NumberOfLatentVariablesParameter.Value.Value; } } public int NumericIntegrationSteps { get { return NumericIntegrationStepsParameter.Value.Value; } } public IEnumerable TrainingEpisodes { get { return TrainingEpisodesParameter.Value; } } public bool OptimizeParametersForEpisodes { get { return OptimizeParametersForEpisodesParameter.Value.Value; } } public string OdeSolver { get { return OdeSolverParameter.Value.Value; } set { var matchingValue = OdeSolverParameter.ValidValues.FirstOrDefault(v => v.Value == value); if (matchingValue == null) throw new ArgumentOutOfRangeException(); else OdeSolverParameter.Value = matchingValue; } } #endregion public event EventHandler ProblemDataChanged; public override bool Maximization { get { return false; } // we minimize NMSE } #region item cloning and persistence // persistence [StorableConstructor] private Problem(StorableConstructorFlag _) : base(_) { } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { if (!Parameters.ContainsKey(OptimizeParametersForEpisodesParameterName)) { Parameters.Add(new FixedValueParameter(OptimizeParametersForEpisodesParameterName, "Flag to select if parameters should be optimized globally or for each episode individually.", new BoolValue(false))); } RegisterEventHandlers(); } // cloning private Problem(Problem original, Cloner cloner) : base(original, cloner) { RegisterEventHandlers(); } public override IDeepCloneable Clone(Cloner cloner) { return new Problem(this, cloner); } #endregion public Problem() : base() { var targetVariables = new CheckedItemList().AsReadOnly(); // HACK: it would be better to provide a new class derived from IDataAnalysisProblem var functions = CreateFunctionSet(); Parameters.Add(new ValueParameter(ProblemDataParameterName, "The data captured from the dynamical system. Use CSV import functionality to import data.", new RegressionProblemData())); Parameters.Add(new ValueParameter>(TargetVariablesParameterName, "Target variables (overrides setting in ProblemData)", targetVariables)); Parameters.Add(new ValueParameter>(FunctionSetParameterName, "The list of allowed functions", functions)); Parameters.Add(new FixedValueParameter(MaximumLengthParameterName, "The maximally allowed length of each expression. Set to a small value (5 - 25). Default = 10", new IntValue(10))); Parameters.Add(new FixedValueParameter(MaximumParameterOptimizationIterationsParameterName, "The maximum number of iterations for optimization of parameters (using L-BFGS). More iterations makes the algorithm slower, fewer iterations might prevent convergence in the optimization scheme. Default = 100", new IntValue(100))); Parameters.Add(new FixedValueParameter(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))); Parameters.Add(new FixedValueParameter(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))); Parameters.Add(new ValueParameter>(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())); Parameters.Add(new FixedValueParameter(OptimizeParametersForEpisodesParameterName, "Flag to select if parameters should be optimized globally or for each episode individually.", new BoolValue(false))); var solversStr = new string[] { "HeuristicLab" /* , "CVODES" */}; var solvers = new ItemSet( solversStr.Select(s => new StringValue(s).AsReadOnly()) ); Parameters.Add(new ConstrainedValueParameter(OdeSolverParameterName, "The solver to use for solving the initial value ODE problems", solvers, solvers.First())); RegisterEventHandlers(); InitAllParameters(); // TODO: use training range as default training episode // TODO: optimization of starting values for latent variables in CVODES solver // TODO: allow to specify the name for the time variable in the dataset and allow variable step-sizes } public override double Evaluate(Individual individual, IRandom random) { var trees = individual.Values.Select(v => v.Value).OfType().ToArray(); // extract all trees from individual var problemData = ProblemData; var targetVars = TargetVariables.CheckedItems.OrderBy(i => i.Index).Select(i => i.Value.Value).ToArray(); 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 if (OptimizeParametersForEpisodes) { throw new NotImplementedException(); int eIdx = 0; double totalNMSE = 0.0; int totalSize = 0; foreach (var episode in TrainingEpisodes) { // double[] optTheta; double nmse = OptimizeForEpisodes(trees, problemData, targetVars, latentVariables, random, new[] { episode }, MaximumParameterOptimizationIterations, NumericIntegrationSteps, OdeSolver); // individual["OptTheta_" + eIdx] = new DoubleArray(optTheta); // write back optimized parameters so that we can use them in the Analysis method eIdx++; totalNMSE += nmse * episode.Size; totalSize += episode.Size; } return totalNMSE / totalSize; } else { // double[] optTheta; double nmse = OptimizeForEpisodes(trees, problemData, targetVars, latentVariables, random, TrainingEpisodes, MaximumParameterOptimizationIterations, NumericIntegrationSteps, OdeSolver); // individual["OptTheta"] = new DoubleArray(optTheta); // write back optimized parameters so that we can use them in the Analysis method return nmse; } } public static double OptimizeForEpisodes( ISymbolicExpressionTree[] trees, IRegressionProblemData problemData, string[] targetVars, string[] latentVariables, IRandom random, IEnumerable episodes, int maxParameterOptIterations, int numericIntegrationSteps, string odeSolver) { // extract constants from trees (without trees for latent variables) var targetVariableTrees = trees.Take(targetVars.Length).ToArray(); var latentVariableTrees = trees.Skip(targetVars.Length).ToArray(); var constantNodes = targetVariableTrees.Select(t => t.IterateNodesPrefix().OfType().ToArray()).ToArray(); var initialTheta = constantNodes.Select(nodes => nodes.Select(n => n.Value).ToArray()).ToArray(); // optimize parameters by fitting f(x,y) to calculated differences dy/dt(t) double nmse = PreTuneParameters(trees, problemData, targetVars, latentVariables, random, episodes, maxParameterOptIterations, initialTheta, out double[] pretunedParameters); // extend parameter vector to include parameters for latent variable trees pretunedParameters = pretunedParameters .Concat(latentVariableTrees .SelectMany(t => t.IterateNodesPrefix().OfType().Select(n => n.Value))) .ToArray(); // optimize parameters using integration of f(x,y) to calculate y(t) nmse = OptimizeParameters(trees, problemData, targetVars, latentVariables, episodes, maxParameterOptIterations, pretunedParameters, numericIntegrationSteps, odeSolver, out double[] optTheta); // var optTheta = pretunedParameters; if (double.IsNaN(nmse) || double.IsInfinity(nmse) || nmse > 100 * trees.Length * episodes.Sum(ep => ep.Size)) return 100 * trees.Length * episodes.Sum(ep => ep.Size); // update tree nodes with optimized values var paramIdx = 0; for (var treeIdx = 0; treeIdx < constantNodes.Length; treeIdx++) { for (int i = 0; i < constantNodes[treeIdx].Length; i++) constantNodes[treeIdx][i].Value = optTheta[paramIdx++]; } return nmse; } private static double PreTuneParameters( ISymbolicExpressionTree[] trees, IRegressionProblemData problemData, string[] targetVars, string[] latentVariables, IRandom random, IEnumerable episodes, int maxParameterOptIterations, double[][] initialTheta, out double[] optTheta) { var thetas = new List(); double nmse = 0.0; var maxTreeNmse = 100 * episodes.Sum(ep => ep.Size); var targetTrees = trees.Take(targetVars.Length).ToArray(); var latentTrees = trees.Take(latentVariables.Length).ToArray(); { // first calculate values of latent variables by integration var inputVariables = targetVars.Concat(latentTrees.SelectMany(t => t.IterateNodesPrefix().OfType().Select(n => n.VariableName))).Except(latentVariables).Distinct(); var myState = new OptimizationData(latentTrees, targetVars, inputVariables.ToArray(), problemData, null, episodes.ToArray(), 10, latentVariables, "HeuristicLab"); var fi = new double[myState.rows.Length * targetVars.Length]; var jac = new double[myState.rows.Length * targetVars.Length, myState.nodeValueLookup.ParameterCount]; var latentValues = new double[myState.rows.Length, latentVariables.Length]; Integrate(myState, fi, jac, latentValues); // add integrated latent variables to dataset var modifiedDataset = ((Dataset)problemData.Dataset).ToModifiable(); foreach (var variable in latentVariables) { modifiedDataset.AddVariable(variable, Enumerable.Repeat(0.0, modifiedDataset.Rows).ToList()); // empty column } int predIdx = 0; foreach (var ep in episodes) { for (int r = ep.Start; r < ep.End; r++) { for (int latVarIdx = 0; latVarIdx < latentVariables.Length; latVarIdx++) { modifiedDataset.SetVariableValue(latentValues[predIdx, latVarIdx], latentVariables[latVarIdx], r); } predIdx++; } } problemData = new RegressionProblemData(modifiedDataset, problemData.AllowedInputVariables, problemData.TargetVariable); } // NOTE: the order of values in parameter matches prefix order of constant nodes in trees for (int treeIdx = 0; treeIdx < targetTrees.Length; treeIdx++) { var t = targetTrees[treeIdx]; var targetValuesDiff = new List(); foreach (var ep in episodes) { var episodeRows = Enumerable.Range(ep.Start, ep.Size); var targetValues = problemData.Dataset.GetDoubleValues(targetVars[treeIdx], episodeRows).ToArray(); targetValuesDiff.AddRange(targetValues.Skip(1).Zip(targetValues, (t1, t0) => t1 - t0));// TODO: smoothing or multi-pole); } var adjustedEpisodes = episodes.Select(ep => new IntRange(ep.Start, ep.End - 1)); // because we lose the last row in the differencing step // data for input variables is assumed to be known // input variables in pretuning are all target variables and all variable names that occur in the tree var inputVariables = targetVars.Concat(t.IterateNodesPrefix().OfType().Select(n => n.VariableName)).Distinct(); var myState = new OptimizationData(new[] { t }, targetVars, inputVariables.ToArray(), problemData, new[] { targetValuesDiff.ToArray() }, adjustedEpisodes.ToArray(), -99, latentVariables, string.Empty); // TODO var paramCount = myState.nodeValueLookup.ParameterCount; optTheta = new double[0]; if (initialTheta[treeIdx].Length > 0) { try { alglib.minlmstate state; alglib.minlmreport report; var p = new double[initialTheta[treeIdx].Length]; var lowerBounds = Enumerable.Repeat(-1000.0, p.Length).ToArray(); var upperBounds = Enumerable.Repeat(1000.0, p.Length).ToArray(); Array.Copy(initialTheta[treeIdx], p, p.Length); alglib.minlmcreatevj(targetValuesDiff.Count, p, out state); alglib.minlmsetcond(state, 0.0, 0.0, 0.0, maxParameterOptIterations); alglib.minlmsetbc(state, lowerBounds, upperBounds); #if DEBUG //alglib.minlmsetgradientcheck(state, 1.0e-7); #endif alglib.minlmoptimize(state, EvaluateObjectiveVector, EvaluateObjectiveVectorAndJacobian, null, myState); alglib.minlmresults(state, out optTheta, out report); if (report.terminationtype < 0) { #if DEBUG if (report.terminationtype == -7) throw new InvalidProgramException("gradient calculation fail!"); #endif optTheta = initialTheta[treeIdx]; } } catch (alglib.alglibexception) { optTheta = initialTheta[treeIdx]; } } var tree_nmse = EvaluateMSE(optTheta, myState); if (double.IsNaN(tree_nmse) || double.IsInfinity(tree_nmse) || tree_nmse > maxTreeNmse) { nmse += maxTreeNmse; thetas.AddRange(initialTheta[treeIdx]); } else { nmse += tree_nmse; thetas.AddRange(optTheta); } } // foreach tree optTheta = thetas.ToArray(); return nmse; } // similar to above but this time we integrate and optimize all parameters for all targets concurrently private static double OptimizeParameters(ISymbolicExpressionTree[] trees, IRegressionProblemData problemData, string[] targetVars, string[] latentVariables, IEnumerable episodes, int maxParameterOptIterations, double[] initialTheta, int numericIntegrationSteps, string odeSolver, out double[] optTheta) { var rowsForDataExtraction = episodes.SelectMany(e => Enumerable.Range(e.Start, e.Size)).ToArray(); var targetValues = new double[targetVars.Length][]; for (int treeIdx = 0; treeIdx < targetVars.Length; treeIdx++) { var t = trees[treeIdx]; targetValues[treeIdx] = problemData.Dataset.GetDoubleValues(targetVars[treeIdx], rowsForDataExtraction).ToArray(); } // data for input variables is assumed to be known // input variables are all variable names that occur in the trees except for target variables (we assume that trees have been generated correctly) var inputVariables = trees.SelectMany(t => t.IterateNodesPrefix().OfType().Select(n => n.VariableName)) .Except(targetVars) .Except(latentVariables) .Distinct(); var myState = new OptimizationData(trees, targetVars, inputVariables.ToArray(), problemData, targetValues, episodes.ToArray(), numericIntegrationSteps, latentVariables, odeSolver); optTheta = initialTheta; if (initialTheta.Length > 0) { var lowerBounds = Enumerable.Repeat(-1000.0, initialTheta.Length).ToArray(); var upperBounds = Enumerable.Repeat(1000.0, initialTheta.Length).ToArray(); try { alglib.minlmstate state; alglib.minlmreport report; alglib.minlmcreatevj(rowsForDataExtraction.Length * trees.Length, initialTheta, out state); alglib.minlmsetbc(state, lowerBounds, upperBounds); alglib.minlmsetcond(state, 0.0, 0.0, 0.0, maxParameterOptIterations); #if DEBUG //alglib.minlmsetgradientcheck(state, 1.0e-7); #endif alglib.minlmoptimize(state, IntegrateAndEvaluateObjectiveVector, IntegrateAndEvaluateObjectiveVectorAndJacobian, null, myState); alglib.minlmresults(state, out optTheta, out report); if (report.terminationtype < 0) { #if DEBUG if (report.terminationtype == -7) throw new InvalidProgramException("gradient calculation fail!"); #endif // there was a problem: reset theta and evaluate for inital values optTheta = initialTheta; } } catch (alglib.alglibexception) { optTheta = initialTheta; } } var nmse = EvaluateIntegratedMSE(optTheta, myState); var maxNmse = 100 * targetValues.Length * rowsForDataExtraction.Length; if (double.IsNaN(nmse) || double.IsInfinity(nmse) || nmse > maxNmse) nmse = maxNmse; return nmse; } // helper public static double EvaluateMSE(double[] x, OptimizationData optimizationData) { var fi = new double[optimizationData.rows.Count()]; EvaluateObjectiveVector(x, fi, optimizationData); return fi.Sum(fii => fii * fii) / fi.Length; } public static void EvaluateObjectiveVector(double[] x, double[] fi, object optimizationData) { EvaluateObjectiveVector(x, fi, (OptimizationData)optimizationData); } // for alglib public static void EvaluateObjectiveVector(double[] x, double[] fi, OptimizationData optimizationData) { var rows = optimizationData.rows; var problemData = optimizationData.problemData; var nodeValueLookup = optimizationData.nodeValueLookup; var ds = problemData.Dataset; var variables = optimizationData.variables; nodeValueLookup.UpdateParamValues(x); int outputIdx = 0; for (int trainIdx = 0; trainIdx < rows.Length; trainIdx++) { // update variable values foreach (var variable in variables) { // in this problem we also allow fixed numeric parameters (represented as variables with the value as name) if (double.TryParse(variable, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) { nodeValueLookup.SetVariableValue(variable, value); // TODO: Perf we don't need to set this for each index } else { nodeValueLookup.SetVariableValue(variable, ds.GetDoubleValue(variable, rows[trainIdx])); // TODO: perf } } // interpret all trees for (int treeIdx = 0; treeIdx < optimizationData.trees.Length; treeIdx++) { var tree = optimizationData.trees[treeIdx]; var pred = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValueLookup); var y = optimizationData.targetValues[treeIdx][trainIdx]; fi[outputIdx++] = (y - pred) * optimizationData.inverseStandardDeviation[treeIdx]; } } } public static void EvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, object optimizationData) { EvaluateObjectiveVectorAndJacobian(x, fi, jac, (OptimizationData)optimizationData); } // for alglib public static void EvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, OptimizationData optimizationData) { // extract variable values from dataset var variableValues = new Dictionary>(); var problemData = optimizationData.problemData; var ds = problemData.Dataset; var rows = optimizationData.rows; var variables = optimizationData.variables; var nodeValueLookup = optimizationData.nodeValueLookup; nodeValueLookup.UpdateParamValues(x); int termIdx = 0; for (int trainIdx = 0; trainIdx < rows.Length; trainIdx++) { // update variable values foreach (var variable in variables) { // in this problem we also allow fixed numeric parameters (represented as variables with the value as name) if (double.TryParse(variable, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) { nodeValueLookup.SetVariableValue(variable, value); // TODO: Perf we don't need to set this for each index } else { nodeValueLookup.SetVariableValue(variable, ds.GetDoubleValue(variable, rows[trainIdx])); // TODO: perf } } var calculatedVariables = optimizationData.targetVariables; var trees = optimizationData.trees; for (int i = 0; i < trees.Length; i++) { var tree = trees[i]; var targetVarName = calculatedVariables[i]; double f; Vector g; InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValueLookup, out f, out g); var y = optimizationData.targetValues[i][trainIdx]; fi[termIdx] = (y - f) * optimizationData.inverseStandardDeviation[i]; // scale of NMSE if (jac != null && g != Vector.Zero) for (int j = 0; j < g.Length; j++) jac[termIdx, j] = -g[j] * optimizationData.inverseStandardDeviation[i]; termIdx++; } } } // helper public static double EvaluateIntegratedMSE(double[] x, OptimizationData optimizationData) { var fi = new double[optimizationData.rows.Count() * optimizationData.targetVariables.Length]; IntegrateAndEvaluateObjectiveVector(x, fi, optimizationData); return fi.Sum(fii => fii * fii) / fi.Length; } public static void IntegrateAndEvaluateObjectiveVector(double[] x, double[] fi, object optimizationData) { IntegrateAndEvaluateObjectiveVector(x, fi, (OptimizationData)optimizationData); } // for alglib public static void IntegrateAndEvaluateObjectiveVector(double[] x, double[] fi, OptimizationData optimizationData) { IntegrateAndEvaluateObjectiveVectorAndJacobian(x, fi, null, optimizationData); } public static void IntegrateAndEvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, object optimizationData) { IntegrateAndEvaluateObjectiveVectorAndJacobian(x, fi, jac, (OptimizationData)optimizationData); } // for alglib public static void IntegrateAndEvaluateObjectiveVectorAndJacobian(double[] x, double[] fi, double[,] jac, OptimizationData optimizationData) { var rows = optimizationData.rows.ToArray(); var problemData = optimizationData.problemData; var nodeValueLookup = optimizationData.nodeValueLookup; var ds = problemData.Dataset; int outputIdx = 0; nodeValueLookup.UpdateParamValues(x); Integrate(optimizationData, fi, jac, null); var trees = optimizationData.trees; // update result with error for (int trainIdx = 0; trainIdx < rows.Length; trainIdx++) { for (int i = 0; i < optimizationData.targetVariables.Length; i++) { var tree = trees[i]; var y = optimizationData.targetValues[i][trainIdx]; fi[outputIdx] = (y - fi[outputIdx]) * optimizationData.inverseStandardDeviation[i]; // scale for normalized squared error if (jac != null) for (int j = 0; j < x.Length; j++) jac[outputIdx, j] = -jac[outputIdx, j] * optimizationData.inverseStandardDeviation[i]; outputIdx++; } } } public override void Analyze(Individual[] individuals, double[] qualities, ResultCollection results, IRandom random) { base.Analyze(individuals, qualities, results, random); if (!results.ContainsKey("Prediction (training)")) { results.Add(new Result("Prediction (training)", typeof(ReadOnlyItemList))); } if (!results.ContainsKey("Prediction (test)")) { results.Add(new Result("Prediction (test)", typeof(ReadOnlyItemList))); } if (!results.ContainsKey("Models")) { results.Add(new Result("Models", typeof(VariableCollection))); } if (!results.ContainsKey("SNMSE")) { results.Add(new Result("SNMSE", typeof(DoubleValue))); } if (!results.ContainsKey("Solution")) { results.Add(new Result("Solution", typeof(Solution))); } if (!results.ContainsKey("Squared error and gradient")) { results.Add(new Result("Squared error and gradient", typeof(DataTable))); } var bestIndividualAndQuality = this.GetBestIndividual(individuals, qualities); var trees = bestIndividualAndQuality.Item1.Values.Select(v => v.Value).OfType().ToArray(); // extract all trees from individual results["SNMSE"].Value = new DoubleValue(bestIndividualAndQuality.Item2); var problemData = ProblemData; var targetVars = TargetVariables.CheckedItems.OrderBy(i => i.Index).Select(i => i.Value.Value).ToArray(); 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 var trainingList = new ItemList(); if (OptimizeParametersForEpisodes) { throw new NotSupportedException(); var eIdx = 0; var trainingPredictions = new List[][]>(); foreach (var episode in TrainingEpisodes) { var episodes = new[] { episode }; var optimizationData = new OptimizationData(trees, targetVars, problemData.AllowedInputVariables.ToArray(), problemData, null, episodes, NumericIntegrationSteps, latentVariables, OdeSolver); var trainingPrediction = Integrate(optimizationData).ToArray(); trainingPredictions.Add(trainingPrediction); eIdx++; } // only for target values var trainingRows = TrainingEpisodes.SelectMany(e => Enumerable.Range(e.Start, e.End - e.Start)); for (int colIdx = 0; colIdx < targetVars.Length; colIdx++) { var targetVar = targetVars[colIdx]; var trainingDataTable = new DataTable(targetVar + " prediction (training)"); var actualValuesRow = new DataRow(targetVar, "The values of " + targetVar, problemData.Dataset.GetDoubleValues(targetVar, trainingRows)); var predictedValuesRow = new DataRow(targetVar + " pred.", "Predicted values for " + targetVar, trainingPredictions.SelectMany(arr => arr.Select(row => row[colIdx].Item1)).ToArray()); trainingDataTable.Rows.Add(actualValuesRow); trainingDataTable.Rows.Add(predictedValuesRow); trainingList.Add(trainingDataTable); } results["Prediction (training)"].Value = trainingList.AsReadOnly(); var models = new VariableCollection(); foreach (var tup in targetVars.Zip(trees, Tuple.Create)) { var targetVarName = tup.Item1; var tree = tup.Item2; var origTreeVar = new HeuristicLab.Core.Variable(targetVarName + "(original)"); origTreeVar.Value = (ISymbolicExpressionTree)tree.Clone(); models.Add(origTreeVar); } results["Models"].Value = models; } else { // data for input variables is assumed to be known // input variables are all variable names that occur in the trees except for target variables (we assume that trees have been generated correctly) var inputVariables = trees .SelectMany(t => t.IterateNodesPrefix().OfType().Select(n => n.VariableName)) .Except(targetVars) .Except(latentVariables) .Distinct(); var optimizationData = new OptimizationData(trees, targetVars, inputVariables.ToArray(), problemData, null, TrainingEpisodes.ToArray(), NumericIntegrationSteps, latentVariables, OdeSolver); var numParams = optimizationData.nodeValueLookup.ParameterCount; var fi = new double[optimizationData.rows.Length * targetVars.Length]; var jac = new double[optimizationData.rows.Length * targetVars.Length, numParams]; var latentValues = new double[optimizationData.rows.Length, latentVariables.Length]; Integrate(optimizationData, fi, jac, latentValues); // for target values and latent variables var trainingRows = optimizationData.rows; for (int colIdx = 0; colIdx < trees.Length; colIdx++) { // is target variable if (colIdx < targetVars.Length) { var targetVar = targetVars[colIdx]; var trainingDataTable = new DataTable(targetVar + " prediction (training)"); var actualValuesRow = new DataRow(targetVar, "The values of " + targetVar, problemData.Dataset.GetDoubleValues(targetVar, trainingRows)); var idx = Enumerable.Range(0, trainingRows.Length).Select(i => i * targetVars.Length + colIdx); var pred = idx.Select(i => fi[i]); var predictedValuesRow = new DataRow(targetVar + " pred.", "Predicted values for " + targetVar, pred.ToArray()); trainingDataTable.Rows.Add(actualValuesRow); trainingDataTable.Rows.Add(predictedValuesRow); for (int paramIdx = 0; paramIdx < numParams; paramIdx++) { var paramSensitivityRow = new DataRow($"∂{targetVar}/∂θ{paramIdx}", $"Sensitivities of parameter {paramIdx}", idx.Select(i => jac[i, paramIdx]).ToArray()); paramSensitivityRow.VisualProperties.SecondYAxis = true; trainingDataTable.Rows.Add(paramSensitivityRow); } trainingList.Add(trainingDataTable); } else { var latentVar = latentVariables[colIdx - targetVars.Length]; var trainingDataTable = new DataTable(latentVar + " prediction (training)"); var idx = Enumerable.Range(0, trainingRows.Length); var pred = idx.Select(i => latentValues[i, colIdx - targetVars.Length]); var predictedValuesRow = new DataRow(latentVar + " pred.", "Predicted values for " + latentVar, pred.ToArray()); var emptyRow = new DataRow(latentVar); trainingDataTable.Rows.Add(emptyRow); trainingDataTable.Rows.Add(predictedValuesRow); trainingList.Add(trainingDataTable); } } var errorTable = new DataTable("Squared error and gradient"); var seRow = new DataRow("Squared error"); var gradientRows = Enumerable.Range(0, numParams).Select(i => new DataRow($"∂SE/∂θ{i}")).ToArray(); errorTable.Rows.Add(seRow); foreach (var gRow in gradientRows) { gRow.VisualProperties.SecondYAxis = true; errorTable.Rows.Add(gRow); } var targetValues = targetVars.Select(v => problemData.Dataset.GetDoubleValues(v, trainingRows).ToArray()).ToArray(); int r = 0; // foreach (var y_pred in trainingPrediction) { // // calculate objective function gradient // double f_i = 0.0; // Vector g_i = Vector.CreateNew(new double[numParams]); // for (int colIdx = 0; colIdx < targetVars.Length; colIdx++) { // var y_pred_f = y_pred[colIdx].Item1; // var y = targetValues[colIdx][r]; // // var res = (y - y_pred_f) * optimizationData.inverseStandardDeviation[colIdx]; // var ressq = res * res; // f_i += ressq; // g_i.Add(y_pred[colIdx].Item2.Scale(-2.0 * res)); // } // seRow.Values.Add(f_i); // for (int j = 0; j < g_i.Length; j++) gradientRows[j].Values.Add(g_i[j]); // r++; // } // results["Squared error and gradient"].Value = errorTable; // TODO: DRY for training and test var testList = new ItemList(); var testRows = ProblemData.TestIndices.ToArray(); var testOptimizationData = new OptimizationData(trees, targetVars, problemData.AllowedInputVariables.ToArray(), problemData, null, new IntRange[] { ProblemData.TestPartition }, NumericIntegrationSteps, latentVariables, OdeSolver); var testPrediction = Integrate(testOptimizationData).ToArray(); for (int colIdx = 0; colIdx < trees.Length; colIdx++) { // is target variable if (colIdx < targetVars.Length) { var targetVar = targetVars[colIdx]; var testDataTable = new DataTable(targetVar + " prediction (test)"); var actualValuesRow = new DataRow(targetVar, "The values of " + targetVar, problemData.Dataset.GetDoubleValues(targetVar, testRows)); var predictedValuesRow = new DataRow(targetVar + " pred.", "Predicted values for " + targetVar, testPrediction.Select(arr => arr[colIdx].Item1).ToArray()); testDataTable.Rows.Add(actualValuesRow); testDataTable.Rows.Add(predictedValuesRow); testList.Add(testDataTable); } else { // var latentVar = latentVariables[colIdx - targetVars.Length]; // var testDataTable = new DataTable(latentVar + " prediction (test)"); // var predictedValuesRow = new DataRow(latentVar + " pred.", "Predicted values for " + latentVar, testPrediction.Select(arr => arr[colIdx].Item1).ToArray()); // var emptyRow = new DataRow(latentVar); // testDataTable.Rows.Add(emptyRow); // testDataTable.Rows.Add(predictedValuesRow); // testList.Add(testDataTable); } } results["Prediction (training)"].Value = trainingList.AsReadOnly(); results["Prediction (test)"].Value = testList.AsReadOnly(); #region simplification of models // TODO the dependency of HeuristicLab.Problems.DataAnalysis.Symbolic is not ideal var models = new VariableCollection(); // to store target var names and original version of tree var clonedTrees = new List(); for (int idx = 0; idx < trees.Length; idx++) { clonedTrees.Add((ISymbolicExpressionTree)trees[idx].Clone()); } var ds = problemData.Dataset; var newProblemData = new RegressionProblemData((IDataset)ds.Clone(), problemData.AllowedInputVariables, problemData.TargetVariable); results["Solution"].Value = new Solution(clonedTrees.ToArray(), // optTheta, newProblemData, targetVars, latentVariables, TrainingEpisodes, OdeSolver, NumericIntegrationSteps); for (int idx = 0; idx < trees.Length; idx++) { var varName = string.Empty; if (idx < targetVars.Length) { varName = targetVars[idx]; } else { varName = latentVariables[idx - targetVars.Length]; } var tree = trees[idx]; var origTreeVar = new HeuristicLab.Core.Variable(varName + "(original)"); origTreeVar.Value = (ISymbolicExpressionTree)tree.Clone(); models.Add(origTreeVar); var simplifiedTreeVar = new HeuristicLab.Core.Variable(varName + "(simplified)"); simplifiedTreeVar.Value = TreeSimplifier.Simplify(tree); models.Add(simplifiedTreeVar); } results["Models"].Value = models; #endregion #region produce classical solutions to allow visualization with PDP for (int treeIdx = 0; treeIdx < trees.Length; treeIdx++) { var t = (ISymbolicExpressionTree)trees[treeIdx].Clone(); var name = targetVars.Concat(latentVariables).ElementAt(treeIdx); // whatever var model = new SymbolicRegressionModel(name + "_diff", t, new SymbolicDataAnalysisExpressionTreeLinearInterpreter()); var solutionDataset = ((Dataset)problemData.Dataset).ToModifiable(); if (treeIdx < targetVars.Length) { var absValues = solutionDataset.GetDoubleValues(name).ToArray(); solutionDataset.AddVariable(name + "_diff", absValues.Skip(1).Zip(absValues, (v1, v0) => v1 - v0).Concat(new double[] { 0.0 }).ToList()); } var solutionProblemData = new RegressionProblemData(solutionDataset, problemData.AllowedInputVariables, name + "_diff"); var solution = model.CreateRegressionSolution(solutionProblemData); results.AddOrUpdateResult("Solution " + name, solution); } #endregion } } #region interpretation // the following uses auto-diff to calculate the gradient w.r.t. the parameters forward in time. // 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. // a comparison of three potential calculation methods for the gradient is given in: // 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 // "Our comparison establishes that the adjoint method is computationally more efficient for numerical estimation of parametric gradients // for state-space models — both linear and non-linear, as in the case of a dynamical causal model (DCM)" // for a solver with the necessary features see: https://computation.llnl.gov/projects/sundials/cvodes public static IEnumerable[]> Integrate(OptimizationData optimizationData) { var nTargets = optimizationData.targetVariables.Length; var n = optimizationData.rows.Length * optimizationData.targetVariables.Length; var d = optimizationData.nodeValueLookup.ParameterCount; double[] fi = new double[n]; double[,] jac = new double[n, d]; Integrate(optimizationData, fi, jac, null); for (int i = 0; i < optimizationData.rows.Length; i++) { var res = new Tuple[nTargets]; for (int j = 0; j < nTargets; j++) { res[j] = Tuple.Create(fi[i * nTargets + j], Vector.CreateFromMatrixRow(jac, i * nTargets + j)); } yield return res; } } public static void Integrate(OptimizationData optimizationData, double[] fi, double[,] jac, double[,] latentValues) { var trees = optimizationData.trees; var dataset = optimizationData.problemData.Dataset; var inputVariables = optimizationData.variables; var targetVariables = optimizationData.targetVariables; var latentVariables = optimizationData.latentVariables; var episodes = optimizationData.episodes; var odeSolver = optimizationData.odeSolver; var numericIntegrationSteps = optimizationData.numericIntegrationSteps; var calculatedVariables = targetVariables.Concat(latentVariables).ToArray(); // TODO: must conincide with the order of trees in the encoding var nodeValues = optimizationData.nodeValueLookup; // TODO: numericIntegrationSteps is only relevant for the HeuristicLab solver var outputRowIdx = 0; var episodeIdx = 0; foreach (var episode in optimizationData.episodes) { var rows = Enumerable.Range(episode.Start, episode.End - episode.Start).ToArray(); var t0 = rows.First(); // initialize values for inputs and targets from dataset foreach (var varName in inputVariables) { // in this problem we also allow fixed numeric parameters (represented as variables with the value as name) if (double.TryParse(varName, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) { nodeValues.SetVariableValue(varName, value, Vector.Zero); } else { var y0 = dataset.GetDoubleValue(varName, t0); nodeValues.SetVariableValue(varName, y0, Vector.Zero); } } foreach (var varName in targetVariables) { var y0 = dataset.GetDoubleValue(varName, t0); nodeValues.SetVariableValue(varName, y0, Vector.Zero); // output starting value fi[outputRowIdx] = y0; Vector.Zero.CopyTo(jac, outputRowIdx); outputRowIdx++; } var latentValueRowIdx = 0; var latentValueColIdx = 0; foreach (var varName in latentVariables) { var y0 = 0.0; // assume we start at zero nodeValues.SetVariableValue(varName, y0, Vector.Zero); if (latentValues != null) { latentValues[latentValueRowIdx, latentValueColIdx++] = y0; } } latentValueColIdx = 0; latentValueRowIdx++; { // CODE BELOW DOESN'T WORK ANYMORE // if (latentVariables.Length > 0) throw new NotImplementedException(); // // // add value entries for latent variables which are also integrated // // initial values are at the end of the parameter vector // // separate initial values for each episode // var initialValueIdx = parameterValues.Length - episodes.Count() * latentVariables.Length + episodeIdx * latentVariables.Length; // foreach (var latentVar in latentVariables) { // var arr = new double[parameterValues.Length]; // backing array // arr[initialValueIdx] = 1.0; // var g = new Vector(arr); // nodeValues.SetVariableValue(latentVar, parameterValues[initialValueIdx], g); // we don't have observations for latent variables therefore we optimize the initial value for each episode // initialValueIdx++; // } } var prevT = t0; // TODO: here we should use a variable for t if it is available. Right now we assume equidistant measurements. foreach (var t in rows.Skip(1)) { if (odeSolver == "HeuristicLab") IntegrateHL(trees, calculatedVariables, nodeValues, numericIntegrationSteps); // integrator updates nodeValues else if (odeSolver == "CVODES") throw new NotImplementedException(); // IntegrateCVODES(trees, calculatedVariables, variableValues, parameterValues, t - prevT); else throw new InvalidOperationException("Unknown ODE solver " + odeSolver); prevT = t; // update output for target variables (TODO: if we want to visualize the latent variables then we need to provide a separate output) for (int i = 0; i < targetVariables.Length; i++) { var targetVar = targetVariables[i]; var yt = nodeValues.GetVariableValue(targetVar); // fill up remaining rows with last valid value if there are invalid values if (double.IsNaN(yt.Item1) || double.IsInfinity(yt.Item1)) { for (; outputRowIdx < fi.Length; outputRowIdx++) { var prevIdx = outputRowIdx - targetVariables.Length; fi[outputRowIdx] = fi[prevIdx]; // current <- prev if (jac != null) for (int j = 0; j < jac.GetLength(1); j++) jac[outputRowIdx, j] = jac[prevIdx, j]; } return; }; fi[outputRowIdx] = yt.Item1; var g = yt.Item2; g.CopyTo(jac, outputRowIdx); outputRowIdx++; } if (latentValues != null) { foreach (var latentVariable in latentVariables) { var lt = nodeValues.GetVariableValue(latentVariable).Item1; latentValues[latentValueRowIdx, latentValueColIdx++] = lt; } latentValueRowIdx++; latentValueColIdx = 0; } // update for next time step (only the inputs) foreach (var varName in inputVariables) { // in this problem we also allow fixed numeric parameters (represented as variables with the value as name) if (double.TryParse(varName, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) { // value is unchanged } else { nodeValues.SetVariableValue(varName, dataset.GetDoubleValue(varName, t), Vector.Zero); } } } episodeIdx++; } } #region CVODES /* /// /// Here we use CVODES to solve the ODE. Forward sensitivities are used to calculate the gradient for parameter optimization /// /// Each equation in the ODE represented as a tree /// The names of the calculated variables /// The start values of the calculated variables as well as their sensitivites over parameters /// The current parameter values /// The time t up to which we need to integrate. private static void IntegrateCVODES( ISymbolicExpressionTree[] trees, // f(y,p) in tree representation string[] calculatedVariables, // names of elements of y Dictionary> variableValues, // y (intput and output) input: y(t0), output: y(t0+t) double[] parameterValues, // p double t // duration t for which we want to integrate ) { // the RHS of the ODE // dy/dt = f(y_t,x_t,p) CVODES.CVRhsFunc f = CreateOdeRhs(trees, calculatedVariables, parameterValues); // the Jacobian ∂f/∂y CVODES.CVDlsJacFunc jac = CreateJac(trees, calculatedVariables, parameterValues); // the RHS for the forward sensitivities (∂f/∂y)s_i(t) + ∂f/∂p_i CVODES.CVSensRhsFn sensF = CreateSensitivityRhs(trees, calculatedVariables, parameterValues); // setup solver int numberOfEquations = trees.Length; IntPtr y = IntPtr.Zero; IntPtr cvode_mem = IntPtr.Zero; IntPtr A = IntPtr.Zero; IntPtr yS0 = IntPtr.Zero; IntPtr linearSolver = IntPtr.Zero; var ns = parameterValues.Length; // number of parameters try { y = CVODES.N_VNew_Serial(numberOfEquations); // init y to current values of variables // y must be initialized before calling CVodeInit for (int i = 0; i < calculatedVariables.Length; i++) { CVODES.NV_Set_Ith_S(y, i, variableValues[calculatedVariables[i]].Item1); } cvode_mem = CVODES.CVodeCreate(CVODES.MultistepMethod.CV_ADAMS, CVODES.NonlinearSolverIteration.CV_FUNCTIONAL); var flag = CVODES.CVodeInit(cvode_mem, f, 0.0, y); Assert(CVODES.CV_SUCCESS == flag); double relTol = 1.0e-2; double absTol = 1.0; flag = CVODES.CVodeSStolerances(cvode_mem, relTol, absTol); // TODO: probably need to adjust absTol per variable Assert(CVODES.CV_SUCCESS == flag); A = CVODES.SUNDenseMatrix(numberOfEquations, numberOfEquations); Assert(A != IntPtr.Zero); linearSolver = CVODES.SUNDenseLinearSolver(y, A); Assert(linearSolver != IntPtr.Zero); flag = CVODES.CVDlsSetLinearSolver(cvode_mem, linearSolver, A); Assert(CVODES.CV_SUCCESS == flag); flag = CVODES.CVDlsSetJacFn(cvode_mem, jac); Assert(CVODES.CV_SUCCESS == flag); yS0 = CVODES.N_VCloneVectorArray_Serial(ns, y); // clone the output vector for each parameter unsafe { // set to initial sensitivities supplied by caller for (int pIdx = 0; pIdx < ns; pIdx++) { var yS0_i = *((IntPtr*)yS0.ToPointer() + pIdx); for (var varIdx = 0; varIdx < calculatedVariables.Length; varIdx++) { CVODES.NV_Set_Ith_S(yS0_i, varIdx, variableValues[calculatedVariables[varIdx]].Item2[pIdx]); } } } flag = CVODES.CVodeSensInit(cvode_mem, ns, CVODES.CV_SIMULTANEOUS, sensF, yS0); Assert(CVODES.CV_SUCCESS == flag); flag = CVODES.CVodeSensEEtolerances(cvode_mem); Assert(CVODES.CV_SUCCESS == flag); // make one forward integration step double tout = 0.0; // first output time flag = CVODES.CVode(cvode_mem, t, y, ref tout, CVODES.CV_NORMAL); if (flag == CVODES.CV_SUCCESS) { Assert(t == tout); // get sensitivities flag = CVODES.CVodeGetSens(cvode_mem, ref tout, yS0); Assert(CVODES.CV_SUCCESS == flag); // update variableValues based on integration results for (int varIdx = 0; varIdx < calculatedVariables.Length; varIdx++) { var yi = CVODES.NV_Get_Ith_S(y, varIdx); var gArr = new double[parameterValues.Length]; for (var pIdx = 0; pIdx < parameterValues.Length; pIdx++) { unsafe { var yS0_pi = *((IntPtr*)yS0.ToPointer() + pIdx); gArr[pIdx] = CVODES.NV_Get_Ith_S(yS0_pi, varIdx); } } variableValues[calculatedVariables[varIdx]] = Tuple.Create(yi, new Vector(gArr)); } } else { variableValues.Clear(); // indicate problems by not returning new values } // cleanup all allocated objects } finally { if (y != IntPtr.Zero) CVODES.N_VDestroy_Serial(y); if (cvode_mem != IntPtr.Zero) CVODES.CVodeFree(ref cvode_mem); if (linearSolver != IntPtr.Zero) CVODES.SUNLinSolFree(linearSolver); if (A != IntPtr.Zero) CVODES.SUNMatDestroy(A); if (yS0 != IntPtr.Zero) CVODES.N_VDestroyVectorArray_Serial(yS0, ns); } } private static CVODES.CVRhsFunc CreateOdeRhs( ISymbolicExpressionTree[] trees, string[] calculatedVariables, double[] parameterValues) { // we don't need to calculate a gradient here return (double t, IntPtr y, // N_Vector, current value of y (input) IntPtr ydot, // N_Vector (calculated value of y' (output) IntPtr user_data // optional user data, (unused here) ) => { // TODO: perf var nodeValues = new Dictionary>(); int pIdx = 0; foreach (var tree in trees) { foreach (var n in tree.IterateNodesPrefix()) { if (IsConstantNode(n)) { nodeValues.Add(n, Tuple.Create(parameterValues[pIdx], Vector.Zero)); // here we do not need a gradient pIdx++; } else if (n.SubtreeCount == 0) { // for variables and latent variables get the value from variableValues var varName = n.Symbol.Name; var varIdx = Array.IndexOf(calculatedVariables, varName); // TODO: perf! if (varIdx < 0) throw new InvalidProgramException(); var y_i = CVODES.NV_Get_Ith_S(y, (long)varIdx); nodeValues.Add(n, Tuple.Create(y_i, Vector.Zero)); // no gradient needed } } } for (int i = 0; i < trees.Length; i++) { var tree = trees[i]; var res_i = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues); CVODES.NV_Set_Ith_S(ydot, i, res_i.Item1); } return 0; }; } private static CVODES.CVDlsJacFunc CreateJac( ISymbolicExpressionTree[] trees, string[] calculatedVariables, double[] parameterValues) { return ( double t, // current time (input) IntPtr y, // N_Vector, current value of y (input) IntPtr fy, // N_Vector, current value of f (input) IntPtr Jac, // SUNMatrix ∂f/∂y (output, rows i contains are ∂f_i/∂y vector) IntPtr user_data, // optional (unused here) IntPtr tmp1, // N_Vector, optional (unused here) IntPtr tmp2, // N_Vector, optional (unused here) IntPtr tmp3 // N_Vector, optional (unused here) ) => { // here we need to calculate partial derivatives for the calculated variables y var nodeValues = new Dictionary>(); int pIdx = 0; foreach (var tree in trees) { foreach (var n in tree.IterateNodesPrefix()) { if (IsConstantNode(n)) { nodeValues.Add(n, Tuple.Create(parameterValues[pIdx], Vector.Zero)); // here we need a gradient over y which is zero for parameters pIdx++; } else if (n.SubtreeCount == 0) { // for variables and latent variables we use values supplied in y and init gradient vectors accordingly var varName = n.Symbol.Name; var varIdx = Array.IndexOf(calculatedVariables, varName); // TODO: perf! if (varIdx < 0) throw new InvalidProgramException(); var y_i = CVODES.NV_Get_Ith_S(y, (long)varIdx); var gArr = new double[CVODES.NV_LENGTH_S(y)]; // backing array gArr[varIdx] = 1.0; var g = new Vector(gArr); nodeValues.Add(n, Tuple.Create(y_i, g)); } } } for (int i = 0; i < trees.Length; i++) { var tree = trees[i]; var res = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues); var g = res.Item2; for (int j = 0; j < calculatedVariables.Length; j++) { CVODES.SUNDenseMatrix_Set(Jac, i, j, g[j]); } } return 0; // on success }; } // to calculate sensitivities RHS for all equations at once // must compute (∂f/∂y)s_i(t) + ∂f/∂p_i and store in ySdot. // Index i refers to parameters, dimensionality of matrix and vectors is number of equations private static CVODES.CVSensRhsFn CreateSensitivityRhs(ISymbolicExpressionTree[] trees, string[] calculatedVariables, double[] parameterValues) { return ( int Ns, // number of parameters double t, // current time IntPtr y, // N_Vector y(t) (input) IntPtr ydot, // N_Vector dy/dt(t) (input) IntPtr yS, // N_Vector*, one vector for each parameter (input) IntPtr ySdot, // N_Vector*, one vector for each parameter (output) IntPtr user_data, // optional (unused here) IntPtr tmp1, // N_Vector, optional (unused here) IntPtr tmp2 // N_Vector, optional (unused here) ) => { // here we need to calculate partial derivatives for the calculated variables y as well as for the parameters var nodeValues = new Dictionary>(); var d = calculatedVariables.Length + parameterValues.Length; // dimensionality of gradient // first collect variable values foreach (var tree in trees) { foreach (var n in tree.IterateNodesPrefix()) { if (IsVariableNode(n)) { // for variables and latent variables we use values supplied in y and init gradient vectors accordingly var varName = n.Symbol.Name; var varIdx = Array.IndexOf(calculatedVariables, varName); // TODO: perf! if (varIdx < 0) throw new InvalidProgramException(); var y_i = CVODES.NV_Get_Ith_S(y, (long)varIdx); var gArr = new double[d]; // backing array gArr[varIdx] = 1.0; var g = new Vector(gArr); nodeValues.Add(n, Tuple.Create(y_i, g)); } } } // then collect constants int pIdx = 0; foreach (var tree in trees) { foreach (var n in tree.IterateNodesPrefix()) { if (IsConstantNode(n)) { var gArr = new double[d]; gArr[calculatedVariables.Length + pIdx] = 1.0; var g = new Vector(gArr); nodeValues.Add(n, Tuple.Create(parameterValues[pIdx], g)); pIdx++; } } } // gradient vector is [∂f/∂y_1, ∂f/∂y_2, ... ∂f/∂yN, ∂f/∂p_1 ... ∂f/∂p_K] for (pIdx = 0; pIdx < Ns; pIdx++) { unsafe { var sDot_pi = *((IntPtr*)ySdot.ToPointer() + pIdx); CVODES.N_VConst_Serial(0.0, sDot_pi); } } for (int i = 0; i < trees.Length; i++) { var tree = trees[i]; var res = InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues); var g = res.Item2; // update ySdot = (∂f/∂y)s_i(t) + ∂f/∂p_i for (pIdx = 0; pIdx < Ns; pIdx++) { unsafe { var sDot_pi = *((IntPtr*)ySdot.ToPointer() + pIdx); var s_pi = *((IntPtr*)yS.ToPointer() + pIdx); var v = CVODES.NV_Get_Ith_S(sDot_pi, i); // (∂f/∂y)s_i(t) var p = 0.0; for (int yIdx = 0; yIdx < calculatedVariables.Length; yIdx++) { p += g[yIdx] * CVODES.NV_Get_Ith_S(s_pi, yIdx); } // + ∂f/∂p_i CVODES.NV_Set_Ith_S(sDot_pi, i, v + p + g[calculatedVariables.Length + pIdx]); } } } return 0; // on success }; } */ #endregion private static void IntegrateHL( ISymbolicExpressionTree[] trees, string[] calculatedVariables, // names of integrated variables NodeValueLookup nodeValues, int numericIntegrationSteps) { double[] deltaF = new double[calculatedVariables.Length]; Vector[] deltaG = new Vector[calculatedVariables.Length]; double h = 1.0 / numericIntegrationSteps; for (int step = 0; step < numericIntegrationSteps; step++) { // evaluate all trees for (int i = 0; i < trees.Length; i++) { var tree = trees[i]; // Root.GetSubtree(0).GetSubtree(0) skips programRoot and startSymbol double f; Vector g; InterpretRec(tree.Root.GetSubtree(0).GetSubtree(0), nodeValues, out f, out g); deltaF[i] = f; deltaG[i] = g; } // update variableValues for next step, trapezoid integration for (int i = 0; i < trees.Length; i++) { var varName = calculatedVariables[i]; var oldVal = nodeValues.GetVariableValue(varName); nodeValues.SetVariableValue(varName, oldVal.Item1 + h * deltaF[i], oldVal.Item2.Add(deltaG[i].Scale(h))); } } } // TODO: use an existing interpreter implementation instead private static double InterpretRec(ISymbolicExpressionTreeNode node, NodeValueLookup nodeValues) { if (node is ConstantTreeNode) { return ((ConstantTreeNode)node).Value; } else if (node is VariableTreeNode) { return nodeValues.NodeValue(node); } else if (node.Symbol is Addition) { var f = InterpretRec(node.GetSubtree(0), nodeValues); for (int i = 1; i < node.SubtreeCount; i++) { f += InterpretRec(node.GetSubtree(i), nodeValues); } return f; } else if (node.Symbol is Multiplication) { var f = InterpretRec(node.GetSubtree(0), nodeValues); for (int i = 1; i < node.SubtreeCount; i++) { f *= InterpretRec(node.GetSubtree(i), nodeValues); } return f; } else if (node.Symbol is Subtraction) { if (node.SubtreeCount == 1) { return -InterpretRec(node.GetSubtree(0), nodeValues); } else { var f = InterpretRec(node.GetSubtree(0), nodeValues); for (int i = 1; i < node.SubtreeCount; i++) { f -= InterpretRec(node.GetSubtree(i), nodeValues); } return f; } } else if (node.Symbol is Division) { if (node.SubtreeCount == 1) { var f = InterpretRec(node.GetSubtree(0), nodeValues); // protected division if (f.IsAlmost(0.0)) { return 0; } else { return 1.0 / f; } } else { var f = InterpretRec(node.GetSubtree(0), nodeValues); for (int i = 1; i < node.SubtreeCount; i++) { var g = InterpretRec(node.GetSubtree(i), nodeValues); // protected division if (g.IsAlmost(0.0)) { return 0; } else { f /= g; } } return f; } } else if (node.Symbol is Sine) { Assert(node.SubtreeCount == 1); var f = InterpretRec(node.GetSubtree(0), nodeValues); return Math.Sin(f); } else if (node.Symbol is Cosine) { Assert(node.SubtreeCount == 1); var f = InterpretRec(node.GetSubtree(0), nodeValues); return Math.Cos(f); } else if (node.Symbol is Square) { Assert(node.SubtreeCount == 1); var f = InterpretRec(node.GetSubtree(0), nodeValues); return f * f; } else if (node.Symbol is Exponential) { Assert(node.SubtreeCount == 1); var f = InterpretRec(node.GetSubtree(0), nodeValues); return Math.Exp(f); } else if (node.Symbol is Logarithm) { Assert(node.SubtreeCount == 1); var f = InterpretRec(node.GetSubtree(0), nodeValues); return Math.Log(f); } else if (node.Symbol is HyperbolicTangent) { Assert(node.SubtreeCount == 1); var f = InterpretRec(node.GetSubtree(0), nodeValues); return Math.Tanh(f); } else if (node.Symbol is AnalyticQuotient) { Assert(node.SubtreeCount == 2); var f = InterpretRec(node.GetSubtree(0), nodeValues); var g = InterpretRec(node.GetSubtree(1), nodeValues); return f / Math.Sqrt(1 + g * g); } else throw new NotSupportedException("unsupported symbol"); } private static void Assert(bool cond) { #if DEBUG if (!cond) throw new InvalidOperationException("Assertion failed"); #endif } private static void InterpretRec( ISymbolicExpressionTreeNode node, NodeValueLookup nodeValues, // contains value and gradient vector for a node (variables and constants only) out double z, out Vector dz ) { double f, g; Vector df, dg; if (node.Symbol is Constant || node.Symbol is Variable) { z = nodeValues.NodeValue(node); dz = Vector.CreateNew(nodeValues.NodeGradient(node)); // original gradient vectors are never changed by evaluation } else if (node.Symbol is Addition) { InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); for (int i = 1; i < node.SubtreeCount; i++) { InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg); f = f + g; df = df.Add(dg); } z = f; dz = df; } else if (node.Symbol is Multiplication) { InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); for (int i = 1; i < node.SubtreeCount; i++) { InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg); f = f * g; df = df.Scale(g).Add(dg.Scale(f)); // f'*g + f*g' } z = f; dz = df; } else if (node.Symbol is Subtraction) { if (node.SubtreeCount == 1) { InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); z = -f; dz = df.Scale(-1.0); } else { InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); for (int i = 1; i < node.SubtreeCount; i++) { InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg); f = f - g; df = df.Subtract(dg); } z = f; dz = df; } } else if (node.Symbol is Division) { if (node.SubtreeCount == 1) { InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); // protected division if (f.IsAlmost(0.0)) { z = 0; dz = Vector.Zero; } else { z = 1.0 / f; dz = df.Scale(-1 * z * z); } } else { InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); for (int i = 1; i < node.SubtreeCount; i++) { InterpretRec(node.GetSubtree(i), nodeValues, out g, out dg); // protected division if (g.IsAlmost(0.0)) { z = 0; dz = Vector.Zero; return; } else { var inv_g = 1.0 / g; f = f * inv_g; df = dg.Scale(-f * inv_g * inv_g).Add(df.Scale(inv_g)); } } z = f; dz = df; } } else if (node.Symbol is Sine) { Assert(node.SubtreeCount == 1); InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); z = Math.Sin(f); dz = df.Scale(Math.Cos(f)); } else if (node.Symbol is Cosine) { Assert(node.SubtreeCount == 1); InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); z = Math.Cos(f); dz = df.Scale(-Math.Sin(f)); } else if (node.Symbol is Square) { Assert(node.SubtreeCount == 1); InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); z = f * f; dz = df.Scale(2.0 * f); } else if (node.Symbol is Exponential) { Assert(node.SubtreeCount == 1); InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); z = Math.Exp(f); dz = df.Scale(Math.Exp(f)); } else if (node.Symbol is Logarithm) { Assert(node.SubtreeCount == 1); InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); z = Math.Log(f); dz = df.Scale(1.0 / f); } else if (node.Symbol is HyperbolicTangent) { Assert(node.SubtreeCount == 1); InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); z = Math.Tanh(f); dz = df.Scale(1 - z * z); // tanh(f(x))' = f(x)'sech²(f(x)) = f(x)'(1 - tanh²(f(x))) } else if (node.Symbol is AnalyticQuotient) { Assert(node.SubtreeCount == 2); InterpretRec(node.GetSubtree(0), nodeValues, out f, out df); InterpretRec(node.GetSubtree(1), nodeValues, out g, out dg); z = f / Math.Sqrt(1 + g * g); var denom = 1.0 / Math.Pow(1 + g * g, 1.5); dz = df.Scale(1 + g * g).Subtract(dg.Scale(f * g)).Scale(denom); } else { throw new NotSupportedException("unsupported symbol"); } } #endregion #region events /* * Dependencies between parameters: * * ProblemData * | * V * TargetVariables FunctionSet MaximumLength NumberOfLatentVariables * | | | | * V V | | * Grammar <---------------+------------------- * | * V * Encoding */ private void RegisterEventHandlers() { ProblemDataParameter.ValueChanged += ProblemDataParameter_ValueChanged; if (ProblemDataParameter.Value != null) ProblemDataParameter.Value.Changed += ProblemData_Changed; TargetVariablesParameter.ValueChanged += TargetVariablesParameter_ValueChanged; if (TargetVariablesParameter.Value != null) TargetVariablesParameter.Value.CheckedItemsChanged += CheckedTargetVariablesChanged; FunctionSetParameter.ValueChanged += FunctionSetParameter_ValueChanged; if (FunctionSetParameter.Value != null) FunctionSetParameter.Value.CheckedItemsChanged += CheckedFunctionsChanged; MaximumLengthParameter.Value.ValueChanged += MaximumLengthChanged; NumberOfLatentVariablesParameter.Value.ValueChanged += NumLatentVariablesChanged; } private void NumLatentVariablesChanged(object sender, EventArgs e) { UpdateGrammarAndEncoding(); } private void MaximumLengthChanged(object sender, EventArgs e) { UpdateGrammarAndEncoding(); } private void FunctionSetParameter_ValueChanged(object sender, EventArgs e) { FunctionSetParameter.Value.CheckedItemsChanged += CheckedFunctionsChanged; } private void CheckedFunctionsChanged(object sender, CollectionItemsChangedEventArgs> e) { UpdateGrammarAndEncoding(); } private void TargetVariablesParameter_ValueChanged(object sender, EventArgs e) { TargetVariablesParameter.Value.CheckedItemsChanged += CheckedTargetVariablesChanged; } private void CheckedTargetVariablesChanged(object sender, CollectionItemsChangedEventArgs> e) { UpdateGrammarAndEncoding(); } private void ProblemDataParameter_ValueChanged(object sender, EventArgs e) { ProblemDataParameter.Value.Changed += ProblemData_Changed; OnProblemDataChanged(); OnReset(); } private void ProblemData_Changed(object sender, EventArgs e) { OnProblemDataChanged(); OnReset(); } private void OnProblemDataChanged() { UpdateTargetVariables(); // implicitly updates other dependent parameters var handler = ProblemDataChanged; if (handler != null) handler(this, EventArgs.Empty); } #endregion #region helper private static IEnumerable EveryNth(IEnumerable xs, int step) { var e = xs.GetEnumerator(); while (e.MoveNext()) { for (int i = 0; i < step; i++) { if (!e.MoveNext()) yield break; } yield return e.Current; } } private void InitAllParameters() { UpdateTargetVariables(); // implicitly updates the grammar and the encoding } private ReadOnlyCheckedItemList CreateFunctionSet() { var l = new CheckedItemList(); l.Add(new StringValue("Addition").AsReadOnly()); l.Add(new StringValue("Multiplication").AsReadOnly()); l.Add(new StringValue("Division").AsReadOnly()); l.Add(new StringValue("Subtraction").AsReadOnly()); l.Add(new StringValue("Sine").AsReadOnly()); l.Add(new StringValue("Cosine").AsReadOnly()); l.Add(new StringValue("Square").AsReadOnly()); l.Add(new StringValue("Logarithm").AsReadOnly()); l.Add(new StringValue("Exponential").AsReadOnly()); l.Add(new StringValue("HyperbolicTangent").AsReadOnly()); l.Add(new StringValue("AnalyticQuotient").AsReadOnly()); return l.AsReadOnly(); } private static bool IsConstantNode(ISymbolicExpressionTreeNode n) { // return n.Symbol.Name[0] == 'θ'; return n is ConstantTreeNode; } private static double GetConstantValue(ISymbolicExpressionTreeNode n) { return ((ConstantTreeNode)n).Value; } private static bool IsLatentVariableNode(ISymbolicExpressionTreeNode n) { return n.Symbol.Name[0] == 'λ'; } private static bool IsVariableNode(ISymbolicExpressionTreeNode n) { return (n.SubtreeCount == 0) && !IsConstantNode(n) && !IsLatentVariableNode(n); } private static string GetVariableName(ISymbolicExpressionTreeNode n) { return ((VariableTreeNode)n).VariableName; } private void UpdateTargetVariables() { var currentlySelectedVariables = TargetVariables.CheckedItems .OrderBy(i => i.Index) .Select(i => i.Value.Value) .ToArray(); var newVariablesList = new CheckedItemList(ProblemData.Dataset.VariableNames.Select(str => new StringValue(str).AsReadOnly()).ToArray()).AsReadOnly(); var matchingItems = newVariablesList.Where(item => currentlySelectedVariables.Contains(item.Value)).ToArray(); foreach (var item in newVariablesList) { if (currentlySelectedVariables.Contains(item.Value)) { newVariablesList.SetItemCheckedState(item, true); } else { newVariablesList.SetItemCheckedState(item, false); } } TargetVariablesParameter.Value = newVariablesList; } private void UpdateGrammarAndEncoding() { var encoding = new MultiEncoding(); var g = CreateGrammar(); foreach (var targetVar in TargetVariables.CheckedItems) { var e = new SymbolicExpressionTreeEncoding(targetVar + "_tree", g, MaximumLength, MaximumLength); var multiManipulator = e.Operators.Where(op => op is MultiSymbolicExpressionTreeManipulator).First(); var filteredOperators = e.Operators.Where(op => !(op is IManipulator)).ToArray(); // make sure our multi-manipulator is the only manipulator e.Operators = new IOperator[] { multiManipulator }.Concat(filteredOperators); // set the crossover probability to reduce likelihood that multiple trees are crossed at the same time var subtreeCrossovers = e.Operators.OfType(); foreach (var xover in subtreeCrossovers) { xover.CrossoverProbability.Value = 0.3; } encoding = encoding.Add(e); // only limit by length } for (int i = 1; i <= NumberOfLatentVariables; i++) { var e = new SymbolicExpressionTreeEncoding("λ" + i + "_tree", g, MaximumLength, MaximumLength); var multiManipulator = e.Operators.Where(op => op is MultiSymbolicExpressionTreeManipulator).First(); var filteredOperators = e.Operators.Where(op => !(op is IManipulator)).ToArray(); // make sure our multi-manipulator is the only manipulator e.Operators = new IOperator[] { multiManipulator }.Concat(filteredOperators); // set the crossover probability to reduce likelihood that multiple trees are crossed at the same time var subtreeCrossovers = e.Operators.OfType(); foreach (var xover in subtreeCrossovers) { xover.CrossoverProbability.Value = 0.3; } encoding = encoding.Add(e); } Encoding = encoding; } private ISymbolicExpressionGrammar CreateGrammar() { var grammar = new TypeCoherentExpressionGrammar(); grammar.StartGrammarManipulation(); var problemData = ProblemData; var ds = problemData.Dataset; grammar.MaximumFunctionArguments = 0; grammar.MaximumFunctionDefinitions = 0; var allowedVariables = problemData.AllowedInputVariables.Concat(TargetVariables.CheckedItems.Select(chk => chk.Value.Value)); foreach (var varSymbol in grammar.Symbols.OfType()) { if (!varSymbol.Fixed) { varSymbol.AllVariableNames = problemData.InputVariables.Select(x => x.Value).Where(x => ds.VariableHasType(x)); varSymbol.VariableNames = allowedVariables.Where(x => ds.VariableHasType(x)); } } foreach (var factorSymbol in grammar.Symbols.OfType()) { if (!factorSymbol.Fixed) { factorSymbol.AllVariableNames = problemData.InputVariables.Select(x => x.Value).Where(x => ds.VariableHasType(x)); factorSymbol.VariableNames = problemData.AllowedInputVariables.Where(x => ds.VariableHasType(x)); factorSymbol.VariableValues = factorSymbol.VariableNames .ToDictionary(varName => varName, varName => ds.GetStringValues(varName).Distinct().ToList()); } } foreach (var factorSymbol in grammar.Symbols.OfType()) { if (!factorSymbol.Fixed) { factorSymbol.AllVariableNames = problemData.InputVariables.Select(x => x.Value).Where(x => ds.VariableHasType(x)); factorSymbol.VariableNames = problemData.AllowedInputVariables.Where(x => ds.VariableHasType(x)); factorSymbol.VariableValues = factorSymbol.VariableNames .ToDictionary(varName => varName, varName => ds.GetStringValues(varName).Distinct() .Select((n, i) => Tuple.Create(n, i)) .ToDictionary(tup => tup.Item1, tup => tup.Item2)); } } grammar.ConfigureAsDefaultRegressionGrammar(); grammar.GetSymbol("Logarithm").Enabled = false; // not supported yet grammar.GetSymbol("Exponential").Enabled = false; // not supported yet // configure initialization of constants var constSy = (Constant)grammar.GetSymbol("Constant"); // max and min are only relevant for initialization constSy.MaxValue = +1.0e-1; // small initial values for constant opt constSy.MinValue = -1.0e-1; constSy.MultiplicativeManipulatorSigma = 1.0; // allow large jumps for manipulation constSy.ManipulatorMu = 0.0; constSy.ManipulatorSigma = 1.0; // allow large jumps // configure initialization of variables var varSy = (Variable)grammar.GetSymbol("Variable"); // fix variable weights to 1.0 varSy.WeightMu = 1.0; varSy.WeightSigma = 0.0; varSy.WeightManipulatorMu = 0.0; varSy.WeightManipulatorSigma = 0.0; varSy.MultiplicativeWeightManipulatorSigma = 0.0; foreach (var f in FunctionSet) { grammar.GetSymbol(f.Value).Enabled = FunctionSet.ItemChecked(f); } grammar.FinishedGrammarManipulation(); return grammar; // // whenever ProblemData is changed we create a new grammar with the necessary symbols // var g = new SimpleSymbolicExpressionGrammar(); // var unaryFunc = new string[] { "sin", "cos", "sqr" }; // var binaryFunc = new string[] { "+", "-", "*", "%" }; // foreach (var func in unaryFunc) { // if (FunctionSet.CheckedItems.Any(ci => ci.Value.Value == func)) g.AddSymbol(func, 1, 1); // } // foreach (var func in binaryFunc) { // if (FunctionSet.CheckedItems.Any(ci => ci.Value.Value == func)) g.AddSymbol(func, 2, 2); // } // // foreach (var variableName in ProblemData.AllowedInputVariables.Union(TargetVariables.CheckedItems.Select(i => i.Value.Value))) // g.AddTerminalSymbol(variableName); // // // generate symbols for numeric parameters for which the value is optimized using AutoDiff // // we generate multiple symbols to balance the probability for selecting a numeric parameter in the generation of random trees // var numericConstantsFactor = 2.0; // for (int i = 0; i < numericConstantsFactor * (ProblemData.AllowedInputVariables.Count() + TargetVariables.CheckedItems.Count()); i++) { // g.AddTerminalSymbol("θ" + i); // numeric parameter for which the value is optimized using AutoDiff // } // // // generate symbols for latent variables // for (int i = 1; i <= NumberOfLatentVariables; i++) { // g.AddTerminalSymbol("λ" + i); // numeric parameter for which the value is optimized using AutoDiff // } // // return g; } #endregion #region Import & Export public void Load(IRegressionProblemData data) { Name = data.Name; Description = data.Description; ProblemData = data; } public IRegressionProblemData Export() { return ProblemData; } #endregion // TODO: for integration we only need a part of the data that we need for optimization public class OptimizationData { public readonly ISymbolicExpressionTree[] trees; public readonly string[] targetVariables; public readonly IRegressionProblemData problemData; public readonly double[][] targetValues; public readonly double[] inverseStandardDeviation; public readonly IntRange[] episodes; public readonly int numericIntegrationSteps; public readonly string[] latentVariables; public readonly string odeSolver; public readonly NodeValueLookup nodeValueLookup; public readonly int[] rows; internal readonly string[] variables; public OptimizationData(ISymbolicExpressionTree[] trees, string[] targetVars, string[] inputVariables, IRegressionProblemData problemData, double[][] targetValues, IntRange[] episodes, int numericIntegrationSteps, string[] latentVariables, string odeSolver) { this.trees = trees; this.targetVariables = targetVars; this.problemData = problemData; this.targetValues = targetValues; this.variables = inputVariables; if (targetValues != null) { this.inverseStandardDeviation = new double[targetValues.Length]; for (int i = 0; i < targetValues.Length; i++) { // calculate variance for each episode separately and calc the average var epStartIdx = 0; var stdevs = new List(); foreach (var ep in episodes) { var epValues = targetValues[i].Skip(epStartIdx).Take(ep.Size); stdevs.Add(epValues.StandardDeviation()); epStartIdx += ep.Size; } inverseStandardDeviation[i] = 1.0 / stdevs.Average(); } } else this.inverseStandardDeviation = Enumerable.Repeat(1.0, trees.Length).ToArray(); this.episodes = episodes; this.numericIntegrationSteps = numericIntegrationSteps; this.latentVariables = latentVariables; this.odeSolver = odeSolver; this.nodeValueLookup = new NodeValueLookup(trees); this.rows = episodes.SelectMany(ep => Enumerable.Range(ep.Start, ep.Size)).ToArray(); } } public class NodeValueLookup { private readonly Dictionary> node2val = new Dictionary>(); private readonly Dictionary> name2nodes = new Dictionary>(); private readonly ConstantTreeNode[] constantNodes; private readonly Vector[] constantGradientVectors; // private readonly Dictionary paramIdx2node = new Dictionary(); public double NodeValue(ISymbolicExpressionTreeNode node) => node2val[node].Item1; public Vector NodeGradient(ISymbolicExpressionTreeNode node) => node2val[node].Item2; public NodeValueLookup(ISymbolicExpressionTree[] trees) { this.constantNodes = trees.SelectMany(t => t.IterateNodesPrefix().OfType()).ToArray(); constantGradientVectors = new Vector[constantNodes.Length]; for (int paramIdx = 0; paramIdx < constantNodes.Length; paramIdx++) { constantGradientVectors[paramIdx] = Vector.CreateIndicator(length: constantNodes.Length, idx: paramIdx); var node = constantNodes[paramIdx]; node2val[node] = Tuple.Create(node.Value, constantGradientVectors[paramIdx]); } foreach (var tree in trees) { foreach (var node in tree.IterateNodesPrefix().Where(IsVariableNode)) { var varName = GetVariableName(node); if (!name2nodes.TryGetValue(varName, out List nodes)) { nodes = new List(); name2nodes.Add(varName, nodes); } nodes.Add(node); SetVariableValue(varName, 0.0); // this value is updated in the prediction loop } } } public int ParameterCount => constantNodes.Length; public void SetVariableValue(string variableName, double val) { SetVariableValue(variableName, val, Vector.Zero); } public Tuple GetVariableValue(string variableName) { return node2val[name2nodes[variableName].First()]; } public void SetVariableValue(string variableName, double val, Vector dVal) { if (name2nodes.TryGetValue(variableName, out List nodes)) { nodes.ForEach(n => node2val[n] = Tuple.Create(val, dVal)); } else { var fakeNode = new VariableTreeNode(new Variable()); fakeNode.Weight = 1.0; fakeNode.VariableName = variableName; var newNodeList = new List(); newNodeList.Add(fakeNode); name2nodes.Add(variableName, newNodeList); node2val[fakeNode] = Tuple.Create(val, dVal); } } internal void UpdateParamValues(double[] x) { for (int i = 0; i < x.Length; i++) { constantNodes[i].Value = x[i]; node2val[constantNodes[i]] = Tuple.Create(x[i], constantGradientVectors[i]); } } } } }