#region License Information /* HeuristicLab * Copyright (C) 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.Collections.ObjectModel; using System.Linq; using HEAL.Attic; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; using HeuristicLab.Parameters; namespace HeuristicLab.Problems.DataAnalysis.Symbolic { [StorableType("DE6C1E1E-D7C1-4070-847E-63B68562B10C")] [Item("IntervalInterpreter", "Interpreter for calculation of intervals of symbolic models.")] public sealed class IntervalInterpreter : ParameterizedNamedItem, IStatefulItem { private const string EvaluatedSolutionsParameterName = "EvaluatedSolutions"; private const string MinSplittingWidthParameterName = "MinSplittingWidth"; private const string MaxSplittingDepthParameterName = "MaxSplittingDepth"; private const string UseIntervalSplittingParameterName = "UseIntervalSplitting"; public IFixedValueParameter EvaluatedSolutionsParameter => (IFixedValueParameter)Parameters[EvaluatedSolutionsParameterName]; public IFixedValueParameter MinSplittingWithParameter => (IFixedValueParameter)Parameters[MinSplittingWidthParameterName]; public IFixedValueParameter MaxSplittingDepthParameter => (IFixedValueParameter)Parameters[MaxSplittingDepthParameterName]; public IFixedValueParameter UseIntervalSplittingParameter => (IFixedValueParameter)Parameters[UseIntervalSplittingParameterName]; public int MinSplittingWidth { get => MinSplittingWithParameter.Value.Value; set => MinSplittingWithParameter.Value.Value = value; } public int MaxSplittingDepth { get => MaxSplittingDepthParameter.Value.Value; set => MaxSplittingDepthParameter.Value.Value = value; } public bool UseIntervalSplitting { get => UseIntervalSplittingParameter.Value.Value; set => UseIntervalSplittingParameter.Value.Value = value; } public int EvaluatedSolutions { get => EvaluatedSolutionsParameter.Value.Value; set => EvaluatedSolutionsParameter.Value.Value = value; } [StorableConstructor] private IntervalInterpreter(StorableConstructorFlag _) : base(_) { } private IntervalInterpreter(IntervalInterpreter original, Cloner cloner) : base(original, cloner) { } public IntervalInterpreter() : base("IntervalInterpreter", "Interpreter for calculation of intervals of symbolic models.") { Parameters.Add(new FixedValueParameter(EvaluatedSolutionsParameterName, "A counter for the total number of solutions the interpreter has evaluated", new IntValue(0))); Parameters.Add(new FixedValueParameter(MinSplittingWidthParameterName, "Minimum interval width until splitting is stopped", new IntValue(0))); Parameters.Add(new FixedValueParameter(MaxSplittingDepthParameterName, "Maximum recursion depth of the splitting", new IntValue(5))); Parameters.Add(new FixedValueParameter(UseIntervalSplittingParameterName, "", new BoolValue(false))); } public override IDeepCloneable Clone(Cloner cloner) { return new IntervalInterpreter(this, cloner); } private readonly object syncRoot = new object(); #region IStatefulItem Members public void InitializeState() { EvaluatedSolutions = 0; } public void ClearState() { } #endregion public Interval GetSymbolicExpressionTreeInterval( ISymbolicExpressionTree tree, IDataset dataset, IEnumerable rows = null) { var variableRanges = DatasetUtil.GetVariableRanges(dataset, rows); return GetSymbolicExpressionTreeInterval(tree, variableRanges); } public Interval GetSymbolicExpressionTreeIntervals( ISymbolicExpressionTree tree, IDataset dataset, out IDictionary nodeIntervals, IEnumerable rows = null) { var variableRanges = DatasetUtil.GetVariableRanges(dataset, rows); return GetSymbolicExpressionTreeIntervals(tree, variableRanges, out nodeIntervals); } public Interval GetSymbolicExpressionTreeInterval( ISymbolicExpressionTree tree, IReadOnlyDictionary variableRanges) { lock (syncRoot) { EvaluatedSolutions++; } Interval outputInterval; if (UseIntervalSplitting) { outputInterval = GetSymbolicExpressionTreeIntervals(tree, variableRanges, out var _); } else { var instructionCount = 0; var instructions = PrepareInterpreterState(tree, variableRanges); outputInterval = Evaluate(instructions, ref instructionCount); } return outputInterval.LowerBound <= outputInterval.UpperBound ? outputInterval : new Interval(outputInterval.UpperBound, outputInterval.LowerBound); } public Interval GetSymbolicExpressionTreeIntervals( ISymbolicExpressionTree tree, IReadOnlyDictionary variableRanges, out IDictionary nodeIntervals) { lock (syncRoot) { EvaluatedSolutions++; } var intervals = new Dictionary(); var instructions = PrepareInterpreterState(tree, variableRanges); Interval outputInterval; if (UseIntervalSplitting) { //var variables = tree.IterateNodesPrefix().OfType().Select(x => x.VariableName).Distinct() // .ToList(); var containsDependencyProblem = ContainsVariableMultipleTimes(tree, out var variables); if (containsDependencyProblem) { var currIndex = 0; var currDepth = 0; IDictionary writeableVariableRanges = variableRanges.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); //outputInterval = EvaluateRecursive(instructions, intervals, writeableVariableRanges, variables, MinSplittingWidth, MaxSplittingDepth, // ref currIndex, ref currDepth, tree); outputInterval = EvaluateWithSplitting(instructions, intervals, writeableVariableRanges, variables); } else { var instructionCount = 0; outputInterval = Evaluate(instructions, ref instructionCount, intervals); } } else { var instructionCount = 0; outputInterval = Evaluate(instructions, ref instructionCount, intervals); } nodeIntervals = new Dictionary(); foreach (var kvp in intervals) { var interval = kvp.Value; if (interval.IsInfiniteOrUndefined || interval.LowerBound <= interval.UpperBound) nodeIntervals.Add(kvp.Key, interval); else nodeIntervals.Add(kvp.Key, new Interval(interval.UpperBound, interval.LowerBound)); } // because of numerical errors the bounds might be incorrect if (outputInterval.IsInfiniteOrUndefined || outputInterval.LowerBound <= outputInterval.UpperBound) return outputInterval; return new Interval(outputInterval.UpperBound, outputInterval.LowerBound); } private static Instruction[] PrepareInterpreterState( ISymbolicExpressionTree tree, IReadOnlyDictionary variableRanges) { if (variableRanges == null) throw new ArgumentNullException("No variablew ranges are present!", nameof(variableRanges)); //Check if all variables used in the tree are present in the dataset foreach (var variable in tree.IterateNodesPrefix().OfType().Select(n => n.VariableName) .Distinct()) if (!variableRanges.ContainsKey(variable)) throw new InvalidOperationException($"No ranges for variable {variable} is present"); var code = SymbolicExpressionTreeCompiler.Compile(tree, OpCodes.MapSymbolToOpCode); foreach (var instr in code.Where(i => i.opCode == OpCodes.Variable)) { var variableTreeNode = (VariableTreeNode)instr.dynamicNode; instr.data = variableRanges[variableTreeNode.VariableName]; } return code; } public static Interval EvaluateWithSplitting(Instruction[] instructions, IDictionary nodeIntervals, IDictionary variableIntervals, List multipleOccurenceVariables) { var savedIntervals = variableIntervals.ToDictionary(entry => entry.Key, entry => entry.Value); var min = FindBound(instructions, nodeIntervals, variableIntervals, multipleOccurenceVariables, minimization: true); var max = FindBound(instructions, nodeIntervals, savedIntervals, multipleOccurenceVariables, minimization: false); return new Interval(min, max); } private static double FindBound(Instruction[] instructions, IDictionary nodeIntervals, IDictionary variableIntervals, List multipleOccurenceVariables, bool minimization = true) { SortedSet prioQ = new SortedSet(); var ic = 0; //Calculate full box IReadOnlyDictionary readonlyRanges = variableIntervals.ToDictionary(k => k.Key, k => k.Value); var interval = Evaluate(instructions, ref ic, nodeIntervals, readonlyRanges); // the order of keys in a dictionary is guaranteed to be the same order as values in a dictionary // https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary.keys?view=netcore-3.1#remarks //var box = variableIntervals.Values; //Box only contains intervals from multiple occurence variables var box = multipleOccurenceVariables.Select(k => variableIntervals[k]); if (minimization) { prioQ.Add(new BoxBound(box, interval.LowerBound)); } else { prioQ.Add(new BoxBound(box, -interval.UpperBound)); } // TODO a fixed limit for depth?! for (var depth = 0; depth < 200; ++depth) { var currentBound = prioQ.Min; prioQ.Remove(currentBound); var newBoxes = Split(currentBound.box); foreach (var newBox in newBoxes) { //var intervalEnum = newBox.GetEnumerator(); //var keyEnum = readonlyRanges.Keys.GetEnumerator(); //while (intervalEnum.MoveNext() & keyEnum.MoveNext()) { // variableIntervals[keyEnum.Current] = intervalEnum.Current; //} //Set the splitted variables var intervalEnum = newBox.GetEnumerator(); foreach (var key in multipleOccurenceVariables) { intervalEnum.MoveNext(); variableIntervals[key] = intervalEnum.Current; } ic = 0; var res = Evaluate(instructions, ref ic, nodeIntervals, new ReadOnlyDictionary(variableIntervals)); if (minimization) { prioQ.Add(new BoxBound(newBox, res.LowerBound)); } else { prioQ.Add(new BoxBound(newBox, -res.UpperBound)); } } } return minimization ? prioQ.First().bound : -prioQ.First().bound; } private static IEnumerable> Split(List box) { var boxes = box.Select(region => region.Split()).Select(split => new List { split.Item1, split.Item2 }) .ToList(); return boxes.CartesianProduct(); } // a multi-dimensional box with an associated bound // boxbounds are ordered first by bound (smaller first), then by size of box (larger first) then by distance of bottom left corner to origin private class BoxBound : IComparable { public List box; public double bound; public BoxBound(IEnumerable box, double bound) { this.box = new List(box); this.bound = bound; } public int CompareTo(BoxBound other) { if (bound != other.bound) return bound.CompareTo(other.bound); var thisSize = box.Aggregate(1.0, (current, dimExtent) => current * dimExtent.Width); var otherSize = other.box.Aggregate(1.0, (current, dimExtent) => current * dimExtent.Width); if (thisSize != otherSize) return -thisSize.CompareTo(otherSize); var thisDist = box.Sum(dimExtent => dimExtent.LowerBound * dimExtent.LowerBound); var otherDist = other.box.Sum(dimExtent => dimExtent.LowerBound * dimExtent.LowerBound); if (thisDist != otherDist) return thisDist.CompareTo(otherDist); // which is smaller first along the dimensions? for (int i = 0; i < box.Count; i++) { if (box[i].LowerBound != other.box[i].LowerBound) return box[i].LowerBound.CompareTo(other.box[i].LowerBound); } return 0; } } public static Interval EvaluateRecursive( Instruction[] instructions, IDictionary nodeIntervals, IDictionary variableIntervals, IList variables, double minWidth, int maxDepth, ref int currIndex, ref int currDepth, ISymbolicExpressionTree tree) { Interval evaluate() { var ic = 0; IReadOnlyDictionary readonlyRanges = new ReadOnlyDictionary(variableIntervals); return Evaluate(instructions, ref ic, nodeIntervals, readonlyRanges); } Interval recurse(ref int idx, ref int depth) { return EvaluateRecursive(instructions, nodeIntervals, variableIntervals, variables, minWidth, maxDepth, ref idx, ref depth, tree); } var v = variables[currIndex]; var x = variableIntervals[v]; if (x.Width < minWidth || currDepth == maxDepth || !MultipleTimes(tree, v)) { if (currIndex + 1 < variables.Count) { currDepth = 0; currIndex++; var z = recurse(ref currIndex, ref currDepth); currIndex--; return z; } return evaluate(); } var t = x.Split(); var xa = t.Item1; var xb = t.Item2; var d = currDepth; currDepth = d + 1; variableIntervals[v] = xa; var ya = recurse(ref currIndex, ref currDepth); currDepth = d + 1; variableIntervals[v] = xb; var yb = recurse(ref currIndex, ref currDepth); variableIntervals[v] = x; // restore interval return ya | yb; } public static Interval Evaluate( Instruction[] instructions, ref int instructionCounter, IDictionary nodeIntervals = null, IReadOnlyDictionary variableIntervals = null) { var currentInstr = instructions[instructionCounter]; //Use ref parameter, because the tree will be iterated through recursively from the left-side branch to the right side //Update instructionCounter, whenever Evaluate is called instructionCounter++; Interval result = null; switch (currentInstr.opCode) { //Variables, Constants, ... case OpCodes.Variable: { var variableTreeNode = (VariableTreeNode)currentInstr.dynamicNode; var weightInterval = new Interval(variableTreeNode.Weight, variableTreeNode.Weight); //var variableInterval = (Interval)currentInstr.data; Interval variableInterval; if (variableIntervals != null && variableIntervals.ContainsKey(variableTreeNode.VariableName)) variableInterval = variableIntervals[variableTreeNode.VariableName]; else variableInterval = (Interval)currentInstr.data; result = Interval.Multiply(variableInterval, weightInterval); break; } case OpCodes.Constant: { var constTreeNode = (ConstantTreeNode)currentInstr.dynamicNode; result = new Interval(constTreeNode.Value, constTreeNode.Value); break; } //Elementary arithmetic rules case OpCodes.Add: { //result = Evaluate(instructions, ref instructionCounter, nodeIntervals); result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); for (var i = 1; i < currentInstr.nArguments; i++) { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Add(result, argumentInterval); } break; } case OpCodes.Sub: { //result = Evaluate(instructions, ref instructionCounter, nodeIntervals); result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); if (currentInstr.nArguments == 1) result = Interval.Multiply(new Interval(-1, -1), result); for (var i = 1; i < currentInstr.nArguments; i++) { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Subtract(result, argumentInterval); } break; } case OpCodes.Mul: { //result = Evaluate(instructions, ref instructionCounter, nodeIntervals); result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); for (var i = 1; i < currentInstr.nArguments; i++) { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); result = Interval.Multiply(result, argumentInterval); } break; } case OpCodes.Div: { //result = Evaluate(instructions, ref instructionCounter, nodeIntervals); result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); if (currentInstr.nArguments == 1) result = Interval.Divide(new Interval(1, 1), result); for (var i = 1; i < currentInstr.nArguments; i++) { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Divide(result, argumentInterval); } break; } //Trigonometric functions case OpCodes.Sin: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Sine(argumentInterval); break; } case OpCodes.Cos: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Cosine(argumentInterval); break; } case OpCodes.Tan: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Tangens(argumentInterval); break; } case OpCodes.Tanh: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.HyperbolicTangent(argumentInterval); break; } //Exponential functions case OpCodes.Log: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Logarithm(argumentInterval); break; } case OpCodes.Exp: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Exponential(argumentInterval); break; } case OpCodes.Square: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Square(argumentInterval); break; } case OpCodes.SquareRoot: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.SquareRoot(argumentInterval); break; } case OpCodes.Cube: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Cube(argumentInterval); break; } case OpCodes.CubeRoot: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.CubicRoot(argumentInterval); break; } case OpCodes.Absolute: { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Absolute(argumentInterval); break; } case OpCodes.AnalyticQuotient: { //result = Evaluate(instructions, ref instructionCounter, nodeIntervals); result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); for (var i = 1; i < currentInstr.nArguments; i++) { //var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals); var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.AnalyticalQuotient(result, argumentInterval); } break; } default: throw new NotSupportedException($"The tree contains the unknown symbol {currentInstr.dynamicNode.Symbol}"); } if (!(nodeIntervals == null || nodeIntervals.ContainsKey(currentInstr.dynamicNode))) nodeIntervals.Add(currentInstr.dynamicNode, result); return result; } private static bool MultipleTimes(ISymbolicExpressionTree tree, string variable) { var varlist = tree.IterateNodesPrefix().OfType().GroupBy(x => x.VariableName); var group = varlist.Select(x => x.Key == variable).Count(); return group > 1; } private static bool ContainsVariableMultipleTimes(ISymbolicExpressionTree tree, out List variables) { variables = new List(); var varlist = tree.IterateNodesPrefix().OfType().GroupBy(x => x.VariableName); foreach (var group in varlist) { if (group.Count() > 1) { variables.Add(group.Key); } } return varlist.Any(group => group.Count() > 1); } public static bool IsCompatible(ISymbolicExpressionTree tree) { var containsUnknownSymbols = ( from n in tree.Root.GetSubtree(0).IterateNodesPrefix() where !(n.Symbol is Variable) && !(n.Symbol is Constant) && !(n.Symbol is StartSymbol) && !(n.Symbol is Addition) && !(n.Symbol is Subtraction) && !(n.Symbol is Multiplication) && !(n.Symbol is Division) && !(n.Symbol is Sine) && !(n.Symbol is Cosine) && !(n.Symbol is Tangent) && !(n.Symbol is HyperbolicTangent) && !(n.Symbol is Logarithm) && !(n.Symbol is Exponential) && !(n.Symbol is Square) && !(n.Symbol is SquareRoot) && !(n.Symbol is Cube) && !(n.Symbol is CubeRoot) && !(n.Symbol is Absolute) && !(n.Symbol is AnalyticQuotient) select n).Any(); return !containsUnknownSymbols; } } }